• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: Commit

Golang implemented sidechain for Bytom


Commit MetaInfo

Revisione8b6064bbf852b6fca4a152665b0c6e877696b1f (tree)
Zeit2020-03-18 14:56:39
AutorPaladz <yzhu101@uott...>
CommiterGitHub

Log Message

Merge branch 'mov' into rollback_test_casesubmit

Ändern Zusammenfassung

Diff

--- a/application/mov/match/match.go
+++ b/application/mov/match/engine.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 {
21- orderBook *OrderBook
22- maxFeeRate float64
23- nodeProgram []byte
18+ orderBook *OrderBook
19+ feeStrategy FeeStrategy
20+ rewardProgram []byte
2421 }
2522
2623 // NewEngine return a new Engine
27-func NewEngine(orderBook *OrderBook, maxFeeRate float64, nodeProgram []byte) *Engine {
28- return &Engine{orderBook: orderBook, maxFeeRate: maxFeeRate, nodeProgram: nodeProgram}
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,38 +62,20 @@ 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, refunds []RefundAssets, fees []*bc.AssetAmount) error {
66+ for _, feeAmount := range fees {
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.nodeProgram))
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
86- }
87-
88- for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
70+ for i, refund := range refunds {
71+ // each trading participant may be refunded multiple assets
72+ for _, assetAmount := range refund {
8973 contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
9074 if err != nil {
9175 return err
9276 }
9377
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
78+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, contractArgs.SellerProgram))
10079 }
10180 }
10281 return nil
@@ -120,17 +99,18 @@ func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
12099
121100 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
122101 txData := &types.TxData{Version: 1}
123- for i, order := range orders {
102+ for _, order := range orders {
124103 input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
125104 txData.Inputs = append(txData.Inputs, input)
105+ }
126106
127- oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
128- if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil {
129- return nil, err
130- }
107+ receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
108+ allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
109+ if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets.Receives); err != nil {
110+ return nil, err
131111 }
132112
133- if err := e.addMatchTxFeeOutput(txData); err != nil {
113+ if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Refunds, allocatedAssets.Fees); err != nil {
134114 return nil, err
135115 }
136116
@@ -143,94 +123,71 @@ func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
143123 return types.NewTx(*txData), nil
144124 }
145125
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())
126+func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts, deductFeeReceives []*bc.AssetAmount) error {
127+ for i, order := range orders {
128+ contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
169129 if err != nil {
170- return nil, err
130+ return err
171131 }
172132
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- }
133+ requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
134+ receivedAmount := receivedAmounts[i].Amount
135+ shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
136+ isPartialTrade := requestAmount > receivedAmount
177137
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- }
138+ setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmounts[i].Amount)
139+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, deductFeeReceives[i].Amount, contractArgs.SellerProgram))
140+ if isPartialTrade {
141+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
185142 }
186143 }
187- return assetFeeMap, nil
144+ return nil
188145 }
189146
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
147+func calcOppositeIndex(size int, selfIdx int) int {
148+ return (selfIdx + 1) % size
207149 }
208150
209151 // CalcRequestAmount is from amount * numerator / ratioDenominator
210-func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
152+func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
211153 res := big.NewInt(0).SetUint64(fromAmount)
212- res.Mul(res, big.NewInt(contractArg.RatioNumerator)).Quo(res, big.NewInt(contractArg.RatioDenominator))
154+ res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
213155 if !res.IsUint64() {
214156 return 0
215157 }
216158 return res.Uint64()
217159 }
218160
219-func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
161+func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
220162 res := big.NewInt(0).SetUint64(receiveAmount)
221- res.Mul(res, big.NewInt(contractArg.RatioDenominator)).Quo(res, big.NewInt(contractArg.RatioNumerator))
163+ res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
222164 if !res.IsUint64() {
223165 return 0
224166 }
225167 return res.Uint64()
226168 }
227169
228-func calcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
229- return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
230-}
170+// CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
171+func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, []*bc.AssetAmount) {
172+ var receivedAmounts, priceDiffs, shouldPayAmounts []*bc.AssetAmount
173+ for i, order := range orders {
174+ requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
175+ oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
176+ receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
177+ shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
178+ receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
179+ shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
180+ }
231181
232-func calcOppositeIndex(size int, selfIdx int) int {
233- return (selfIdx + 1) % size
182+ for i, receivedAmount := range receivedAmounts {
183+ oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
184+ if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
185+ assetId := oppositeShouldPayAmount.AssetId
186+ amount := oppositeShouldPayAmount.Amount - receivedAmount.Amount
187+ priceDiffs = append(priceDiffs, &bc.AssetAmount{AssetId: assetId, Amount: amount})
188+ }
189+ }
190+ return receivedAmounts, priceDiffs
234191 }
235192
236193 // IsMatched check does the orders can be exchange
@@ -259,29 +216,6 @@ func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position i
259216 txInput.SetArguments(arguments)
260217 }
261218
262-func validateTradePairs(tradePairs []*common.TradePair) error {
263- if len(tradePairs) < 2 {
264- return errors.New("size of trade pairs at least 2")
265- }
266-
267- assetMap := make(map[string]bool)
268- for _, tradePair := range tradePairs {
269- assetMap[tradePair.FromAssetID.String()] = true
270- if *tradePair.FromAssetID == *tradePair.ToAssetID {
271- return errors.New("from asset id can't equal to asset id")
272- }
273- }
274-
275- for _, tradePair := range tradePairs {
276- key := tradePair.ToAssetID.String()
277- if _, ok := assetMap[key]; !ok {
278- return errors.New("invalid trade pairs")
279- }
280- delete(assetMap, key)
281- }
282- return nil
283-}
284-
285219 func sortOrders(orders []*common.Order) []*common.Order {
286220 if len(orders) == 0 {
287221 return nil
@@ -305,3 +239,26 @@ func sortOrders(orders []*common.Order) []*common.Order {
305239 }
306240 return sortedOrders
307241 }
242+
243+func validateTradePairs(tradePairs []*common.TradePair) error {
244+ if len(tradePairs) < 2 {
245+ return errors.New("size of trade pairs at least 2")
246+ }
247+
248+ assetMap := make(map[string]bool)
249+ for _, tradePair := range tradePairs {
250+ assetMap[tradePair.FromAssetID.String()] = true
251+ if *tradePair.FromAssetID == *tradePair.ToAssetID {
252+ return errors.New("from asset id can't equal to asset id")
253+ }
254+ }
255+
256+ for _, tradePair := range tradePairs {
257+ key := tradePair.ToAssetID.String()
258+ if _, ok := assetMap[key]; !ok {
259+ return errors.New("invalid trade pairs")
260+ }
261+ delete(assetMap, key)
262+ }
263+ return nil
264+}
--- a/application/mov/match/match_test.go
+++ b/application/mov/match/engine_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) {
@@ -76,11 +75,21 @@ func TestGenerateMatchedTxs(t *testing.T) {
7675 mock.MatchedTxs[6],
7776 },
7877 },
78+ {
79+ desc: "multiple assets as a fee",
80+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
81+ initStoreOrders: []*common.Order{
82+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[3],
83+ },
84+ wantMatchedTxs: []*types.Tx{
85+ mock.MatchedTxs[11],
86+ },
87+ },
7988 }
8089
8190 for i, c := range cases {
8291 movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
83- matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), 0.05, mock.NodeProgram)
92+ matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), NewDefaultFeeStrategy(), mock.RewardProgram)
8493 var gotMatchedTxs []*types.Tx
8594 for matchEngine.HasMatchedTx(c.tradePairs...) {
8695 matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
@@ -96,19 +105,19 @@ func TestGenerateMatchedTxs(t *testing.T) {
96105 continue
97106 }
98107
99- for i, gotMatchedTx := range gotMatchedTxs {
108+ for j, gotMatchedTx := range gotMatchedTxs {
100109 if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
101110 t.Fatal(err)
102111 }
103112
104- c.wantMatchedTxs[i].Version = 1
105- byteData, err := c.wantMatchedTxs[i].MarshalText()
113+ c.wantMatchedTxs[j].Version = 1
114+ byteData, err := c.wantMatchedTxs[j].MarshalText()
106115 if err != nil {
107116 t.Fatal(err)
108117 }
109118
110- c.wantMatchedTxs[i].SerializedSize = uint64(len(byteData))
111- wantMatchedTx := types.NewTx(c.wantMatchedTxs[i].TxData)
119+ c.wantMatchedTxs[j].SerializedSize = uint64(len(byteData))
120+ wantMatchedTx := types.NewTx(c.wantMatchedTxs[j].TxData)
112121 if gotMatchedTx.ID != wantMatchedTx.ID {
113122 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())
114123 }
@@ -116,45 +125,6 @@ func TestGenerateMatchedTxs(t *testing.T) {
116125 }
117126 }
118127
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-
158128 func TestValidateTradePairs(t *testing.T) {
159129 cases := []struct {
160130 desc string
--- /dev/null
+++ b/application/mov/match/fee_strategy.go
@@ -0,0 +1,108 @@
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+ // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
12+ ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
13+)
14+
15+// AllocatedAssets represent reallocated assets after calculating fees
16+type AllocatedAssets struct {
17+ Receives []*bc.AssetAmount
18+ Refunds []RefundAssets
19+ Fees []*bc.AssetAmount
20+}
21+
22+// RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
23+type RefundAssets []*bc.AssetAmount
24+
25+// FeeStrategy used to indicate how to charge a matching fee
26+type FeeStrategy interface {
27+ // Allocate will allocate the price differential in matching transaction to the participants and the fee
28+ // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
29+ // @param priceDiffs price differential of matching transaction
30+ // @return reallocated assets after calculating fees
31+ Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
32+
33+ // Validate verify that the fee charged for a matching transaction is correct
34+ Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
35+}
36+
37+// DefaultFeeStrategy represent the default fee charge strategy
38+type DefaultFeeStrategy struct {}
39+
40+// NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
41+func NewDefaultFeeStrategy() *DefaultFeeStrategy {
42+ return &DefaultFeeStrategy{}
43+}
44+
45+// Allocate will allocate the price differential in matching transaction to the participants and the fee
46+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
47+ feeMap := make(map[bc.AssetID]uint64)
48+ for _, priceDiff := range priceDiffs {
49+ feeMap[*priceDiff.AssetId] = priceDiff.Amount
50+ }
51+
52+ var fees []*bc.AssetAmount
53+ refunds := make([]RefundAssets, len(receiveAmounts))
54+ receives := make([]*bc.AssetAmount, len(receiveAmounts))
55+
56+ for i, receiveAmount := range receiveAmounts {
57+ amount := receiveAmount.Amount
58+ minFeeAmount := d.calcMinFeeAmount(amount)
59+ receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: amount - minFeeAmount}
60+ feeMap[*receiveAmount.AssetId] += minFeeAmount
61+
62+ maxFeeAmount := d.calcMaxFeeAmount(amount)
63+ feeAmount, reminder := feeMap[*receiveAmount.AssetId], uint64(0)
64+ if feeAmount > maxFeeAmount {
65+ reminder = feeAmount - maxFeeAmount
66+ feeAmount = maxFeeAmount
67+ }
68+
69+ fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
70+
71+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
72+ averageAmount := reminder / uint64(len(receiveAmounts))
73+ if averageAmount == 0 {
74+ averageAmount = 1
75+ }
76+
77+ for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
78+ refundAmount := averageAmount
79+ if j == len(receiveAmounts)-1 {
80+ refundAmount = reminder
81+ }
82+ refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
83+ reminder -= averageAmount
84+ }
85+ }
86+ return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
87+}
88+
89+// Validate verify that the fee charged for a matching transaction is correct
90+func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
91+ for _, receiveAmount := range receiveAmounts {
92+ feeAmount := feeAmounts[*receiveAmount.AssetId]
93+ maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
94+ minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
95+ if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
96+ return ErrAmountOfFeeOutOfRange
97+ }
98+ }
99+ return nil
100+}
101+
102+func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
103+ return uint64(math.Ceil(float64(amount) / 1000))
104+}
105+
106+func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
107+ return uint64(math.Ceil(float64(amount) * 0.05))
108+}
--- a/application/mov/mock/mock.go
+++ b/application/mov/mock/mock.go
@@ -10,11 +10,11 @@ import (
1010 )
1111
1212 var (
13- BTC = bc.NewAssetID([32]byte{1})
14- ETH = bc.NewAssetID([32]byte{2})
15- EOS = bc.NewAssetID([32]byte{3})
16- ETC = bc.NewAssetID([32]byte{4})
17- NodeProgram = []byte{0x58}
13+ BTC = bc.NewAssetID([32]byte{1})
14+ ETH = bc.NewAssetID([32]byte{2})
15+ EOS = bc.NewAssetID([32]byte{3})
16+ ETC = bc.NewAssetID([32]byte{4})
17+ RewardProgram = []byte{0x58}
1818
1919 Btc2EthOrders = []*common.Order{
2020 {
@@ -104,6 +104,18 @@ var (
104104 ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
105105 },
106106 },
107+ {
108+ FromAssetID: &ETH,
109+ ToAssetID: &BTC,
110+ RatioNumerator: 1,
111+ RatioDenominator: 150,
112+ Utxo: &common.MovUtxo{
113+ SourceID: hashPtr(testutil.MustDecodeHash("82752cda63c877a8529d7a7461da6096673e45b3e0b019ce44aa18687ad20445")),
114+ SourcePos: 0,
115+ Amount: 600,
116+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"), 1, 150.0),
117+ },
118+ },
107119 }
108120
109121 Eos2EtcOrders = []*common.Order{
@@ -253,10 +265,13 @@ var (
253265 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, Eth2BtcOrders[1].Utxo.ControlProgram),
254266 },
255267 Outputs: []*types.TxOutput{
256- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
268+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
257269 // re-order
258270 types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
259- types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
271+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
272+ // fee
273+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
274+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
260275 },
261276 }),
262277
@@ -267,9 +282,11 @@ var (
267282 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[0].Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.SourcePos, Eth2BtcOrders[0].Utxo.ControlProgram),
268283 },
269284 Outputs: []*types.TxOutput{
270- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
271- types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
272- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, NodeProgram),
285+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
286+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
287+ // fee
288+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 11, RewardProgram),
289+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
273290 },
274291 }),
275292
@@ -280,15 +297,16 @@ var (
280297 types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, Eth2BtcOrders[2].Utxo.ControlProgram),
281298 },
282299 Outputs: []*types.TxOutput{
283- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
284- types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
300+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
301+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
285302 // re-order
286303 types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
287304 // fee
288- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 27, NodeProgram),
305+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram),
306+ types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 1, RewardProgram),
289307 // refund
290- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 6, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
291- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
308+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
309+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
292310 },
293311 }),
294312 types.NewTx(types.TxData{
@@ -297,10 +315,13 @@ var (
297315 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("39bdb7058a0c31fb740af8e3c382bf608efff1b041cd4dd461332722ad24552a"), *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram),
298316 },
299317 Outputs: []*types.TxOutput{
300- types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 270, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
318+ types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 269, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
301319 // re-order
302320 types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, 15, Btc2EthOrders[1].Utxo.ControlProgram),
303- types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 5, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
321+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 4, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
322+ // fee
323+ types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 1, RewardProgram),
324+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
304325 },
305326 }),
306327
@@ -311,10 +332,13 @@ var (
311332 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[1], 0).Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, 0, Eth2BtcOrders[1].Utxo.ControlProgram),
312333 },
313334 Outputs: []*types.TxOutput{
314- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
335+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
315336 // re-order
316337 types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
317- types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
338+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
339+ // fee
340+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
341+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
318342 },
319343 }),
320344
@@ -325,8 +349,11 @@ var (
325349 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Etc2EosMakerTxs[0], 0).Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, Etc2EosOrders[0].Utxo.ControlProgram),
326350 },
327351 Outputs: []*types.TxOutput{
328- types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 50, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
329- types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
352+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
353+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
354+ // fee
355+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
356+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
330357 },
331358 }),
332359
@@ -338,9 +365,13 @@ var (
338365 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eos2BtcOrders[0].Utxo.SourceID, *Eos2BtcOrders[0].FromAssetID, Eos2BtcOrders[0].Utxo.Amount, Eos2BtcOrders[0].Utxo.SourcePos, Eos2BtcOrders[0].Utxo.ControlProgram),
339366 },
340367 Outputs: []*types.TxOutput{
341- types.NewIntraChainOutput(ETH, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
342- types.NewIntraChainOutput(EOS, 1000, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
343- types.NewIntraChainOutput(BTC, 10, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
368+ types.NewIntraChainOutput(ETH, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
369+ types.NewIntraChainOutput(EOS, 999, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
370+ types.NewIntraChainOutput(BTC, 9, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
371+ // fee
372+ types.NewIntraChainOutput(ETH, 1, RewardProgram),
373+ types.NewIntraChainOutput(EOS, 1, RewardProgram),
374+ types.NewIntraChainOutput(BTC, 1, RewardProgram),
344375 },
345376 }),
346377
@@ -351,10 +382,13 @@ var (
351382 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[0], 0).Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, 0, Eth2BtcOrders[0].Utxo.ControlProgram),
352383 },
353384 Outputs: []*types.TxOutput{
354- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
355- types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 2, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
385+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
386+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
356387 // re-order
357388 types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, 404, Eth2BtcOrders[0].Utxo.ControlProgram),
389+ // fee
390+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
391+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
358392 },
359393 }),
360394
@@ -365,12 +399,13 @@ var (
365399 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, Eth2BtcOrders[2].Utxo.ControlProgram),
366400 },
367401 Outputs: []*types.TxOutput{
368- types.NewIntraChainOutput(*Btc2EthOrders[3].ToAssetID, 810, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
402+ types.NewIntraChainOutput(*Btc2EthOrders[3].ToAssetID, 809, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
369403 // re-order
370404 types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 1, Btc2EthOrders[3].Utxo.ControlProgram),
371- types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 15, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
405+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 14, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
372406 // fee
373- types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 1, NodeProgram),
407+ types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 2, RewardProgram),
408+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
374409 },
375410 }),
376411
@@ -381,8 +416,11 @@ var (
381416 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Etc2EosOrders[0].Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, Etc2EosOrders[0].Utxo.ControlProgram),
382417 },
383418 Outputs: []*types.TxOutput{
384- types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 50, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
385- types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
419+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
420+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
421+ // fee
422+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
423+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
386424 },
387425 }),
388426
@@ -393,9 +431,32 @@ var (
393431 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[0], 0).Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, 0, Eth2BtcOrders[0].Utxo.ControlProgram),
394432 },
395433 Outputs: []*types.TxOutput{
396- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
397- types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
398- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, NodeProgram),
434+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
435+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
436+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, RewardProgram),
437+ // fee
438+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
439+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
440+ },
441+ }),
442+
443+ // full matched transaction from Btc2EthOrders[0] Eth2BtcOrders[3]
444+ types.NewTx(types.TxData{
445+ Inputs: []*types.TxInput{
446+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
447+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[3].Utxo.SourceID, *Eth2BtcOrders[3].FromAssetID, Eth2BtcOrders[3].Utxo.Amount, Eth2BtcOrders[3].Utxo.SourcePos, Eth2BtcOrders[3].Utxo.ControlProgram),
448+ },
449+ Outputs: []*types.TxOutput{
450+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
451+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
452+ // fee
453+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 25, RewardProgram),
454+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 1, RewardProgram),
455+ // refund
456+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
457+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
458+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
459+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
399460 },
400461 }),
401462 }
--- a/application/mov/mov_core.go
+++ b/application/mov/mov_core.go
@@ -1,10 +1,13 @@
11 package mov
22
33 import (
4+ "encoding/hex"
5+
46 "github.com/bytom/vapor/application/mov/common"
57 "github.com/bytom/vapor/application/mov/contract"
68 "github.com/bytom/vapor/application/mov/database"
79 "github.com/bytom/vapor/application/mov/match"
10+ "github.com/bytom/vapor/consensus"
811 "github.com/bytom/vapor/consensus/segwit"
912 dbm "github.com/bytom/vapor/database/leveldb"
1013 "github.com/bytom/vapor/errors"
@@ -12,20 +15,19 @@ import (
1215 "github.com/bytom/vapor/protocol/bc/types"
1316 )
1417
15-const maxFeeRate = 0.05
16-
1718 var (
18- errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
19- errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
20- errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
21- errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
22- errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
23- errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
24- errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
25- errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
26- errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
27- errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
28- errNotMatchedOrder = errors.New("order in matched tx is not matched")
19+ errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
20+ errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
21+ errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
22+ errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
23+ errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
24+ errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
25+ errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
26+ errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
27+ errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
28+ errNotMatchedOrder = errors.New("order in matched tx is not matched")
29+ errNotConfiguredRewardProgram = errors.New("reward program is not configured properly")
30+ errRewardProgramIsWrong = errors.New("the reward program is not correct")
2931 )
3032
3133 // MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
@@ -76,7 +78,7 @@ func (m *MovCore) ApplyBlock(block *types.Block) error {
7678 }
7779
7880 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
79-func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
81+func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
8082 if blockHeight <= m.startBlockHeight {
8183 return nil, nil
8284 }
@@ -86,7 +88,13 @@ func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, block
8688 return nil, err
8789 }
8890
89- matchEngine := match.NewEngine(orderBook, maxFeeRate, nodeProgram)
91+ program, _ := getRewardProgram(blockHeight)
92+ rewardProgram, err := hex.DecodeString(program)
93+ if err != nil {
94+ return nil, errNotConfiguredRewardProgram
95+ }
96+
97+ matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
9098 tradePairIterator := database.NewTradePairIterator(m.movStore)
9199 matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
92100 return matchCollector.result()
@@ -140,13 +148,8 @@ func (m *MovCore) StartHeight() uint64 {
140148 // ValidateBlock no need to verify the block header, because the first module has been verified.
141149 // just need to verify the transactions in the block.
142150 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
143- return m.ValidateTxs(block.Transactions, verifyResults)
144-}
145-
146-// ValidateTxs validate the trade transaction.
147-func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
148- for i, tx := range txs {
149- if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
151+ for i, tx := range block.Transactions {
152+ if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
150153 return err
151154 }
152155 }
@@ -154,9 +157,9 @@ func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResul
154157 }
155158
156159 // ValidateTx validate one transaction.
157-func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
160+func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
158161 if common.IsMatchedTx(tx) {
159- if err := validateMatchedTx(tx, verifyResult); err != nil {
162+ if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil {
160163 return err
161164 }
162165 } else if common.IsCancelOrderTx(tx) {
@@ -180,6 +183,41 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro
180183 return nil
181184 }
182185
186+// matchedTxFee is object to record the mov tx's fee information
187+type matchedTxFee struct {
188+ rewardProgram []byte
189+ amount uint64
190+}
191+
192+// calcFeeAmount return the amount of fee in the matching transaction
193+func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
194+ assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
195+ dealProgMaps := make(map[string]bool)
196+
197+ for _, input := range matchedTx.Inputs {
198+ assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
199+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
200+ if err != nil {
201+ return nil, err
202+ }
203+
204+ dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
205+ }
206+
207+ for _, output := range matchedTx.Outputs {
208+ assetAmount := output.AssetAmount()
209+ if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
210+ assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
211+ if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
212+ delete(assetFeeMap, *assetAmount.AssetId)
213+ }
214+ } else {
215+ assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
216+ }
217+ }
218+ return assetFeeMap, nil
219+}
220+
183221 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
184222 if verifyResult.StatusFail {
185223 return errStatusFailMustFalse
@@ -211,13 +249,13 @@ func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte
211249 return errRatioOfTradeLessThanZero
212250 }
213251
214- if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
252+ if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
215253 return errRequestAmountMath
216254 }
217255 return nil
218256 }
219257
220-func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
258+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
221259 if verifyResult.StatusFail {
222260 return errStatusFailMustFalse
223261 }
@@ -246,21 +284,34 @@ func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
246284 return errAssetIDMustUniqueInMatchedTx
247285 }
248286
249- return validateMatchedTxFeeAmount(tx)
287+ return validateMatchedTxFee(tx, blockHeight)
250288 }
251289
252-func validateMatchedTxFeeAmount(tx *types.Tx) error {
253- txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
290+func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
291+ matchedTxFees, err := calcFeeAmount(tx)
254292 if err != nil {
255293 return err
256294 }
257295
258- for _, amount := range txFee {
259- if amount.FeeAmount > amount.MaxFeeAmount {
260- return errAmountOfFeeGreaterThanMaximum
296+ for _, fee := range matchedTxFees {
297+ if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
298+ return err
261299 }
262300 }
263- return nil
301+
302+ orders, err := getDeleteOrdersFromTx(tx)
303+ if err != nil {
304+ return err
305+ }
306+
307+ receivedAmount, _ := match.CalcReceivedAmount(orders)
308+ feeAmounts := make(map[bc.AssetID]uint64)
309+ for assetID, fee := range matchedTxFees {
310+ feeAmounts[assetID] = fee.amount
311+ }
312+
313+ feeStrategy := match.NewDefaultFeeStrategy()
314+ return feeStrategy.Validate(receivedAmount, feeAmounts)
264315 }
265316
266317 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
@@ -441,3 +492,31 @@ func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*commo
441492 }
442493 return addOrders, deleteOrders
443494 }
495+
496+// getRewardProgram return the reward program by specified block height
497+// if no reward program configured, then will return empty string
498+// if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
499+// the second return value represent whether to find exactly
500+func getRewardProgram(height uint64) (string, bool) {
501+ rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
502+ if len(rewardPrograms) == 0 {
503+ return "", false
504+ }
505+
506+ var program string
507+ for _, rewardProgram := range rewardPrograms {
508+ program = rewardProgram.Program
509+ if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
510+ return program, true
511+ }
512+ }
513+ return program, false
514+}
515+
516+func validateRewardProgram(height uint64, program string) error {
517+ rewardProgram, exact := getRewardProgram(height)
518+ if exact && rewardProgram != program {
519+ return errRewardProgramIsWrong
520+ }
521+ return nil
522+}
--- a/application/mov/mov_core_test.go
+++ b/application/mov/mov_core_test.go
@@ -1,12 +1,14 @@
11 package mov
22
33 import (
4+ "encoding/hex"
45 "math"
56 "os"
67 "testing"
78
89 "github.com/bytom/vapor/application/mov/common"
910 "github.com/bytom/vapor/application/mov/database"
11+ "github.com/bytom/vapor/application/mov/match"
1012 "github.com/bytom/vapor/application/mov/mock"
1113 "github.com/bytom/vapor/consensus"
1214 dbm "github.com/bytom/vapor/database/leveldb"
@@ -287,6 +289,14 @@ func TestApplyBlock(t *testing.T) {
287289 }
288290
289291 func TestValidateBlock(t *testing.T) {
292+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
293+ {
294+ BeginBlock: 0,
295+ EndBlock: 100,
296+ Program: hex.EncodeToString(mock.RewardProgram),
297+ },
298+ }
299+
290300 cases := []struct {
291301 desc string
292302 block *types.Block
@@ -362,9 +372,11 @@ func TestValidateBlock(t *testing.T) {
362372 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
363373 },
364374 Outputs: []*types.TxOutput{
365- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
366- types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
367- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
375+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
376+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
377+ // fee
378+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
379+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
368380 },
369381 }),
370382 },
@@ -383,9 +395,10 @@ func TestValidateBlock(t *testing.T) {
383395 types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
384396 },
385397 Outputs: []*types.TxOutput{
386- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
387- types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
388- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
398+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
399+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
400+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
401+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
389402 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
390403 },
391404 }),
@@ -405,10 +418,11 @@ func TestValidateBlock(t *testing.T) {
405418 types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
406419 },
407420 Outputs: []*types.TxOutput{
408- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
409- types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
410- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
411- types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
421+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
422+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
423+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
424+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
425+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
412426 },
413427 }),
414428 },
@@ -427,7 +441,7 @@ func TestValidateBlock(t *testing.T) {
427441 },
428442 Outputs: []*types.TxOutput{
429443 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
430- types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
444+ types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
431445 },
432446 }),
433447 },
@@ -445,18 +459,19 @@ func TestValidateBlock(t *testing.T) {
445459 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),
446460 },
447461 Outputs: []*types.TxOutput{
448- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
449- types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
462+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
463+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
450464 // re-order
451465 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
452466 // fee
453- types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
467+ types.NewIntraChainOutput(*mock.Btc2EthOrders[2].ToAssetID, 41, mock.RewardProgram),
468+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 1, mock.RewardProgram),
454469 },
455470 }),
456471 },
457472 },
458473 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
459- wantError: errAmountOfFeeGreaterThanMaximum,
474+ wantError: match.ErrAmountOfFeeOutOfRange,
460475 },
461476 {
462477 desc: "ratio numerator is zero",
@@ -507,7 +522,63 @@ func TestValidateBlock(t *testing.T) {
507522 }
508523 }
509524
525+func TestCalcMatchedTxFee(t *testing.T) {
526+ cases := []struct {
527+ desc string
528+ tx types.TxData
529+ maxFeeRate float64
530+ wantMatchedTxFee map[bc.AssetID]*matchedTxFee
531+ }{
532+ {
533+ desc: "fee less than max fee",
534+ maxFeeRate: 0.05,
535+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
536+ mock.ETH: {amount: 11, rewardProgram: mock.RewardProgram},
537+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
538+ },
539+ tx: mock.MatchedTxs[1].TxData,
540+ },
541+ {
542+ desc: "fee refund in tx",
543+ maxFeeRate: 0.05,
544+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
545+ mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram},
546+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
547+ },
548+ tx: mock.MatchedTxs[2].TxData,
549+ },
550+ {
551+ desc: "no price diff",
552+ maxFeeRate: 0.05,
553+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
554+ mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
555+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
556+ },
557+ tx: mock.MatchedTxs[0].TxData,
558+ },
559+ }
560+
561+ for i, c := range cases {
562+ gotMatchedTxFee, err := calcFeeAmount(types.NewTx(c.tx))
563+ if err != nil {
564+ t.Fatal(err)
565+ }
566+
567+ if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
568+ t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
569+ }
570+ }
571+}
572+
510573 func TestBeforeProposalBlock(t *testing.T) {
574+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
575+ {
576+ BeginBlock: 0,
577+ EndBlock: 100,
578+ Program: hex.EncodeToString(mock.RewardProgram),
579+ },
580+ }
581+
511582 cases := []struct {
512583 desc string
513584 initOrders []*common.Order
@@ -572,7 +643,7 @@ func TestBeforeProposalBlock(t *testing.T) {
572643 }
573644
574645 movCore := &MovCore{movStore: store}
575- gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, []byte{0x51}, 2, c.gasLeft, func() bool { return false })
646+ gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, 2, c.gasLeft, func() bool { return false })
576647 if err != nil {
577648 t.Fatal(err)
578649 }
--- a/consensus/general.go
+++ b/consensus/general.go
@@ -75,6 +75,13 @@ type ProducerSubsidy struct {
7575 Subsidy uint64
7676 }
7777
78+// MovRewardProgram is a reward address corresponding to the range of the specified block height when matching transactions
79+type MovRewardProgram struct {
80+ BeginBlock uint64
81+ EndBlock uint64
82+ Program string
83+}
84+
7885 // Params store the config for different network
7986 type Params struct {
8087 // Name defines a human-readable identifier for the network.
@@ -103,7 +110,12 @@ type Params struct {
103110 ProducerSubsidys []ProducerSubsidy
104111
105112 SoftForkPoint map[uint64]uint64
113+
114+ // Mov will only start when the block height is greater than this value
106115 MovStartHeight uint64
116+
117+ // Used to receive rewards for matching transactions
118+ MovRewardPrograms []MovRewardProgram
107119 }
108120
109121 // ActiveNetParams is the active NetParams
@@ -145,7 +157,8 @@ var MainNetParams = Params{
145157 ProducerSubsidys: []ProducerSubsidy{
146158 {BeginBlock: 1, EndBlock: 63072000, Subsidy: 9512938},
147159 },
148- SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600},
160+ SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600},
161+ MovStartHeight: 42000000,
149162 }
150163
151164 // TestNetParams is the config for vapor-testnet
--- a/proposal/proposal.go
+++ b/proposal/proposal.go
@@ -102,7 +102,7 @@ func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) e
102102 continue
103103 }
104104
105- results, gasLeft := preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
105+ results, gasLeft := b.preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
106106 for _, result := range results {
107107 if result.err != nil && !result.gasOnly {
108108 log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("mining block generation: skip tx due to")
@@ -139,11 +139,6 @@ func (b *blockBuilder) applyTransactionFromPool() error {
139139 }
140140
141141 func (b *blockBuilder) applyTransactionFromSubProtocol() error {
142- cp, err := b.accountManager.GetCoinbaseControlProgram()
143- if err != nil {
144- return err
145- }
146-
147142 isTimeout := func() bool {
148143 return b.getTimeoutStatus() > timeoutOk
149144 }
@@ -153,7 +148,7 @@ func (b *blockBuilder) applyTransactionFromSubProtocol() error {
153148 break
154149 }
155150
156- subTxs, err := p.BeforeProposalBlock(b.block.Transactions, cp, b.block.Height, b.gasLeft, isTimeout)
151+ subTxs, err := p.BeforeProposalBlock(b.block.Transactions, b.block.Height, b.gasLeft, isTimeout)
157152 if err != nil {
158153 log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
159154 continue
@@ -294,7 +289,7 @@ type validateTxResult struct {
294289 err error
295290 }
296291
297-func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
292+func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
298293 var results []*validateTxResult
299294 bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
300295 bcTxs := make([]*bc.Tx, len(txs))
@@ -328,7 +323,7 @@ func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoView
328323 continue
329324 }
330325
331- if err := validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
326+ if err := b.validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
332327 results = append(results, &validateTxResult{tx: txs[i], err: err})
333328 continue
334329 }
@@ -339,10 +334,10 @@ func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoView
339334 return results, gasLeft
340335 }
341336
342-func validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
337+func (b *blockBuilder) validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
343338 for _, subProtocol := range subProtocols {
344339 verifyResult := &bc.TxVerifyResult{StatusFail: statusFail}
345- if err := subProtocol.ValidateTx(tx, verifyResult); err != nil {
340+ if err := subProtocol.ValidateTx(tx, verifyResult, b.block.Height); err != nil {
346341 return err
347342 }
348343 }
--- a/protocol/bbft.go
+++ b/protocol/bbft.go
@@ -142,8 +142,13 @@ func (c *Chain) validateSign(block *types.Block) error {
142142
143143 if err := c.checkNodeSign(&block.BlockHeader, node, block.Get(node.Order)); err == errDoubleSignBlock {
144144 log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String(), "pubKey": pubKey}).Warn("the consensus node double sign the same height of different block")
145- block.BlockWitness.Delete(node.Order)
146- continue
145+ // if the blocker double sign & become the mainchain, that means
146+ // all the side chain will reject the main chain make the chain
147+ // fork. All the node will ban each other & can't roll back
148+ if blocker != pubKey {
149+ block.BlockWitness.Delete(node.Order)
150+ continue
151+ }
147152 } else if err != nil {
148153 return err
149154 }
--- a/protocol/protocol.go
+++ b/protocol/protocol.go
@@ -22,11 +22,10 @@ const (
2222 type Protocoler interface {
2323 Name() string
2424 StartHeight() uint64
25- BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
25+ BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
2626 ChainStatus() (uint64, *bc.Hash, error)
2727 ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error
28- ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error
29- ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error
28+ ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error
3029 ApplyBlock(block *types.Block) error
3130 DetachBlock(block *types.Block) error
3231 }
--- a/protocol/tx.go
+++ b/protocol/tx.go
@@ -54,7 +54,7 @@ func (c *Chain) validateTx(tx *types.Tx, bh *types.BlockHeader) (bool, error) {
5454
5555 txVerifyResult := &bc.TxVerifyResult{StatusFail: err != nil}
5656 for _, p := range c.subProtocols {
57- if err := p.ValidateTx(tx, txVerifyResult); err != nil {
57+ if err := p.ValidateTx(tx, txVerifyResult, bh.Height); err != nil {
5858 c.txPool.AddErrCache(&tx.ID, err)
5959 return false, err
6060 }
--- a/protocol/validation/block.go
+++ b/protocol/validation/block.go
@@ -101,6 +101,13 @@ func ValidateBlock(b *bc.Block, parent *types.BlockHeader, rewards []state.Coinb
101101 return errors.Wrapf(validateResult.err, "validate of transaction %d of %d", i, len(b.Transactions))
102102 }
103103
104+ // for support flash swap running on vapor, status fail txs need to be
105+ // rejected. Or the attacker can steal BTM from any BTM/* trade pair by
106+ // using status fail charge fee rule.
107+ if b.Height >= consensus.ActiveNetParams.MovStartHeight && validateResult.err != nil {
108+ return errors.New("the chain currently didn't support status fail tx")
109+ }
110+
104111 if err := b.TransactionStatus.SetStatus(i, validateResult.err != nil); err != nil {
105112 return err
106113 }
--- a/protocol/validation/tx_test.go
+++ b/protocol/validation/tx_test.go
@@ -922,8 +922,10 @@ func TestMagneticContractTx(t *testing.T) {
922922 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
923923 },
924924 Outputs: []*types.TxOutput{
925- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
926- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
925+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
926+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
927+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
928+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
927929 },
928930 }),
929931 },
@@ -942,9 +944,11 @@ func TestMagneticContractTx(t *testing.T) {
942944 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
943945 },
944946 Outputs: []*types.TxOutput{
945- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
947+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
946948 types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, programSeller),
947- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
949+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
950+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
951+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
948952 },
949953 }),
950954 },
@@ -963,9 +967,11 @@ func TestMagneticContractTx(t *testing.T) {
963967 types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer),
964968 },
965969 Outputs: []*types.TxOutput{
966- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
967- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
970+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
971+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
968972 types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer),
973+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
974+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
969975 },
970976 }),
971977 },
@@ -1063,9 +1069,11 @@ func TestMagneticContractTx(t *testing.T) {
10631069 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
10641070 },
10651071 Outputs: []*types.TxOutput{
1066- types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
1072+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
10671073 types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, []byte{0x55}),
1068- types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
1074+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
1075+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
1076+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
10691077 },
10701078 }),
10711079 },
@@ -1139,9 +1147,12 @@ func TestRingMagneticContractTx(t *testing.T) {
11391147 types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram),
11401148 },
11411149 Outputs: []*types.TxOutput{
1142- types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000000, aliceArgs.SellerProgram),
1143- types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000000, bobArgs.SellerProgram),
1144- types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000000, jackArgs.SellerProgram),
1150+ types.NewIntraChainOutput(aliceArgs.RequestedAsset, 199800000, aliceArgs.SellerProgram),
1151+ types.NewIntraChainOutput(bobArgs.RequestedAsset, 399600000, bobArgs.SellerProgram),
1152+ types.NewIntraChainOutput(jackArgs.RequestedAsset, 99900000, jackArgs.SellerProgram),
1153+ types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000, []byte{0x51}),
1154+ types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000, []byte{0x51}),
1155+ types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000, []byte{0x51}),
11451156 },
11461157 }),
11471158 },
--- a/protocol/vm/vmutil/script.go
+++ b/protocol/vm/vmutil/script.go
@@ -166,14 +166,16 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
166166 // standardProgram: Program,
167167 // sellerKey: PublicKey) locks valueAmount of valueAsset {
168168 // clause partialTrade(exchangeAmount: Amount) {
169-// define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
170-// verify actualAmount > 0 && actualAmount < valueAmount
171-// lock exchangeAmount of requestedAsset with sellerProgram
169+// define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
170+// verify actualAmount > 0 && actualAmount < valueAmount
171+// define receiveAmount: Integer = exchangeAmount * 999 / 1000
172+// lock receiveAmount of requestedAsset with sellerProgram
172173 // lock valueAmount-actualAmount of valueAsset with standardProgram
173174 // unlock actualAmount of valueAsset
174175 // }
175176 // clause fullTrade() {
176177 // define requestedAmount: Integer = valueAmount * ratioNumerator / ratioDenominator
178+// define requestedAmount: Integer = requestedAmount * 999 / 1000
177179 // verify requestedAmount > 0
178180 // lock requestedAmount of requestedAsset with sellerProgram
179181 // unlock valueAmount of valueAsset
@@ -219,12 +221,15 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
219221 // TOALTSTACK [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
220222 // 6 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> 6]
221223 // ROLL [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount]
222-// 3 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 3]
223-// ROLL [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset]
224-// 1 [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset 1]
225-// 5 [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset 1 5]
226-// ROLL [... sellerKey standardProgram actualAmount <position> exchangeAmount requestedAsset 1 sellerProgram]
227-// CHECKOUTPUT [... sellerKey standardProgram actualAmount checkOutput(exchangeAmount, requestedAsset, sellerProgram)]
224+// 999 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 999]
225+// 1000 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 1000]
226+// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount]
227+// 3 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount 3]
228+// ROLL [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset]
229+// 1 [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1]
230+// 5 [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1 5]
231+// ROLL [... sellerKey standardProgram actualAmount <position> receiveAmount requestedAsset 1 sellerProgram]
232+// CHECKOUTPUT [... sellerKey standardProgram actualAmount checkOutput(receiveAmount, requestedAsset, sellerProgram)]
228233 // VERIFY [... sellerKey standardProgram actualAmount]
229234 // FROMALTSTACK [... sellerKey standardProgram actualAmount <position>]
230235 // 1 [... sellerKey standardProgram actualAmount <position> 1]
@@ -246,6 +251,9 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
246251 // 3 [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioNumerator 3]
247252 // ROLL [... sellerKey standardProgram sellerProgram requestedAsset valueAmount ratioNumerator ratioDenominator]
248253 // MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
254+// 999 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999]
255+// 1000 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999 1000]
256+// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
249257 // DUP [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount]
250258 // 0 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount 0]
251259 // GREATERTHAN [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)]
@@ -315,6 +323,9 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
315323 builder.AddOp(vm.OP_TOALTSTACK)
316324 builder.AddOp(vm.OP_6)
317325 builder.AddOp(vm.OP_ROLL)
326+ builder.AddInt64(999)
327+ builder.AddInt64(1000)
328+ builder.AddOp(vm.OP_MULFRACTION)
318329 builder.AddOp(vm.OP_3)
319330 builder.AddOp(vm.OP_ROLL)
320331 builder.AddOp(vm.OP_1)
@@ -340,6 +351,9 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
340351 builder.AddOp(vm.OP_3)
341352 builder.AddOp(vm.OP_ROLL)
342353 builder.AddOp(vm.OP_MULFRACTION)
354+ builder.AddInt64(999)
355+ builder.AddInt64(1000)
356+ builder.AddOp(vm.OP_MULFRACTION)
343357 builder.AddOp(vm.OP_DUP)
344358 builder.AddOp(vm.OP_0)
345359 builder.AddOp(vm.OP_GREATERTHAN)
Show on old repository browser