Golang implemented sidechain for Bytom
Revision | e8b6064bbf852b6fca4a152665b0c6e877696b1f (tree) |
---|---|
Zeit | 2020-03-18 14:56:39 |
Autor | Paladz <yzhu101@uott...> |
Commiter | GitHub |
Merge branch 'mov' into rollback_test_casesubmit
@@ -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 | - orderBook *OrderBook | |
22 | - maxFeeRate float64 | |
23 | - nodeProgram []byte | |
18 | + orderBook *OrderBook | |
19 | + feeStrategy FeeStrategy | |
20 | + rewardProgram []byte | |
24 | 21 | } |
25 | 22 | |
26 | 23 | // 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} | |
29 | 26 | } |
30 | 27 | |
31 | 28 | // 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 | ||
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, 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)) | |
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.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 { | |
89 | 73 | contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram()) |
90 | 74 | if err != nil { |
91 | 75 | return err |
92 | 76 | } |
93 | 77 | |
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)) | |
100 | 79 | } |
101 | 80 | } |
102 | 81 | return nil |
@@ -120,17 +99,18 @@ func (e *Engine) addPartialTradeOrder(tx *types.Tx) error { | ||
120 | 99 | |
121 | 100 | func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { |
122 | 101 | txData := &types.TxData{Version: 1} |
123 | - for i, order := range orders { | |
102 | + for _, order := range orders { | |
124 | 103 | input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram) |
125 | 104 | txData.Inputs = append(txData.Inputs, input) |
105 | + } | |
126 | 106 | |
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 | |
131 | 111 | } |
132 | 112 | |
133 | - if err := e.addMatchTxFeeOutput(txData); err != nil { | |
113 | + if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Refunds, allocatedAssets.Fees); err != nil { | |
134 | 114 | return nil, err |
135 | 115 | } |
136 | 116 |
@@ -143,94 +123,71 @@ func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { | ||
143 | 123 | return types.NewTx(*txData), nil |
144 | 124 | } |
145 | 125 | |
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) | |
169 | 129 | if err != nil { |
170 | - return nil, err | |
130 | + return err | |
171 | 131 | } |
172 | 132 | |
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 | |
177 | 137 | |
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)) | |
185 | 142 | } |
186 | 143 | } |
187 | - return assetFeeMap, nil | |
144 | + return nil | |
188 | 145 | } |
189 | 146 | |
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 | |
207 | 149 | } |
208 | 150 | |
209 | 151 | // CalcRequestAmount is from amount * numerator / ratioDenominator |
210 | -func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
152 | +func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 { | |
211 | 153 | 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)) | |
213 | 155 | if !res.IsUint64() { |
214 | 156 | return 0 |
215 | 157 | } |
216 | 158 | return res.Uint64() |
217 | 159 | } |
218 | 160 | |
219 | -func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
161 | +func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 { | |
220 | 162 | 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)) | |
222 | 164 | if !res.IsUint64() { |
223 | 165 | return 0 |
224 | 166 | } |
225 | 167 | return res.Uint64() |
226 | 168 | } |
227 | 169 | |
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 | + } | |
231 | 181 | |
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 | |
234 | 191 | } |
235 | 192 | |
236 | 193 | // IsMatched check does the orders can be exchange |
@@ -259,29 +216,6 @@ func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position i | ||
259 | 216 | txInput.SetArguments(arguments) |
260 | 217 | } |
261 | 218 | |
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 | - | |
285 | 219 | func sortOrders(orders []*common.Order) []*common.Order { |
286 | 220 | if len(orders) == 0 { |
287 | 221 | return nil |
@@ -305,3 +239,26 @@ func sortOrders(orders []*common.Order) []*common.Order { | ||
305 | 239 | } |
306 | 240 | return sortedOrders |
307 | 241 | } |
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 | +} |
@@ -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) { |
@@ -76,11 +75,21 @@ func TestGenerateMatchedTxs(t *testing.T) { | ||
76 | 75 | mock.MatchedTxs[6], |
77 | 76 | }, |
78 | 77 | }, |
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 | + }, | |
79 | 88 | } |
80 | 89 | |
81 | 90 | for i, c := range cases { |
82 | 91 | 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) | |
84 | 93 | var gotMatchedTxs []*types.Tx |
85 | 94 | for matchEngine.HasMatchedTx(c.tradePairs...) { |
86 | 95 | matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...) |
@@ -96,19 +105,19 @@ func TestGenerateMatchedTxs(t *testing.T) { | ||
96 | 105 | continue |
97 | 106 | } |
98 | 107 | |
99 | - for i, gotMatchedTx := range gotMatchedTxs { | |
108 | + for j, gotMatchedTx := range gotMatchedTxs { | |
100 | 109 | if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil { |
101 | 110 | t.Fatal(err) |
102 | 111 | } |
103 | 112 | |
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() | |
106 | 115 | if err != nil { |
107 | 116 | t.Fatal(err) |
108 | 117 | } |
109 | 118 | |
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) | |
112 | 121 | if gotMatchedTx.ID != wantMatchedTx.ID { |
113 | 122 | 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 | 123 | } |
@@ -116,45 +125,6 @@ func TestGenerateMatchedTxs(t *testing.T) { | ||
116 | 125 | } |
117 | 126 | } |
118 | 127 | |
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 | 128 | func TestValidateTradePairs(t *testing.T) { |
159 | 129 | cases := []struct { |
160 | 130 | desc string |
@@ -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 | +} |
@@ -10,11 +10,11 @@ import ( | ||
10 | 10 | ) |
11 | 11 | |
12 | 12 | 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} | |
18 | 18 | |
19 | 19 | Btc2EthOrders = []*common.Order{ |
20 | 20 | { |
@@ -104,6 +104,18 @@ var ( | ||
104 | 104 | ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0), |
105 | 105 | }, |
106 | 106 | }, |
107 | + { | |
108 | + FromAssetID: Ð, | |
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 | + }, | |
107 | 119 | } |
108 | 120 | |
109 | 121 | Eos2EtcOrders = []*common.Order{ |
@@ -253,10 +265,13 @@ var ( | ||
253 | 265 | 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), |
254 | 266 | }, |
255 | 267 | Outputs: []*types.TxOutput{ |
256 | - types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")), | |
268 | + types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")), | |
257 | 269 | // re-order |
258 | 270 | 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), | |
260 | 275 | }, |
261 | 276 | }), |
262 | 277 |
@@ -267,9 +282,11 @@ var ( | ||
267 | 282 | 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), |
268 | 283 | }, |
269 | 284 | 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), | |
273 | 290 | }, |
274 | 291 | }), |
275 | 292 |
@@ -280,15 +297,16 @@ var ( | ||
280 | 297 | 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), |
281 | 298 | }, |
282 | 299 | 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")), | |
285 | 302 | // re-order |
286 | 303 | types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram), |
287 | 304 | // 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), | |
289 | 307 | // 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")), | |
292 | 310 | }, |
293 | 311 | }), |
294 | 312 | types.NewTx(types.TxData{ |
@@ -297,10 +315,13 @@ var ( | ||
297 | 315 | types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("39bdb7058a0c31fb740af8e3c382bf608efff1b041cd4dd461332722ad24552a"), *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram), |
298 | 316 | }, |
299 | 317 | Outputs: []*types.TxOutput{ |
300 | - types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 270, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")), | |
318 | + types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 269, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")), | |
301 | 319 | // re-order |
302 | 320 | 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), | |
304 | 325 | }, |
305 | 326 | }), |
306 | 327 |
@@ -311,10 +332,13 @@ var ( | ||
311 | 332 | 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), |
312 | 333 | }, |
313 | 334 | Outputs: []*types.TxOutput{ |
314 | - types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")), | |
335 | + types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")), | |
315 | 336 | // re-order |
316 | 337 | 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), | |
318 | 342 | }, |
319 | 343 | }), |
320 | 344 |
@@ -325,8 +349,11 @@ var ( | ||
325 | 349 | 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), |
326 | 350 | }, |
327 | 351 | 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), | |
330 | 357 | }, |
331 | 358 | }), |
332 | 359 |
@@ -338,9 +365,13 @@ var ( | ||
338 | 365 | 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), |
339 | 366 | }, |
340 | 367 | 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), | |
344 | 375 | }, |
345 | 376 | }), |
346 | 377 |
@@ -351,10 +382,13 @@ var ( | ||
351 | 382 | 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), |
352 | 383 | }, |
353 | 384 | 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")), | |
356 | 387 | // re-order |
357 | 388 | 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), | |
358 | 392 | }, |
359 | 393 | }), |
360 | 394 |
@@ -365,12 +399,13 @@ var ( | ||
365 | 399 | 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), |
366 | 400 | }, |
367 | 401 | Outputs: []*types.TxOutput{ |
368 | - types.NewIntraChainOutput(*Btc2EthOrders[3].ToAssetID, 810, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")), | |
402 | + types.NewIntraChainOutput(*Btc2EthOrders[3].ToAssetID, 809, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")), | |
369 | 403 | // re-order |
370 | 404 | 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")), | |
372 | 406 | // 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), | |
374 | 409 | }, |
375 | 410 | }), |
376 | 411 |
@@ -381,8 +416,11 @@ var ( | ||
381 | 416 | 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), |
382 | 417 | }, |
383 | 418 | 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), | |
386 | 424 | }, |
387 | 425 | }), |
388 | 426 |
@@ -393,9 +431,32 @@ var ( | ||
393 | 431 | 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), |
394 | 432 | }, |
395 | 433 | 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")), | |
399 | 460 | }, |
400 | 461 | }), |
401 | 462 | } |
@@ -1,10 +1,13 @@ | ||
1 | 1 | package mov |
2 | 2 | |
3 | 3 | import ( |
4 | + "encoding/hex" | |
5 | + | |
4 | 6 | "github.com/bytom/vapor/application/mov/common" |
5 | 7 | "github.com/bytom/vapor/application/mov/contract" |
6 | 8 | "github.com/bytom/vapor/application/mov/database" |
7 | 9 | "github.com/bytom/vapor/application/mov/match" |
10 | + "github.com/bytom/vapor/consensus" | |
8 | 11 | "github.com/bytom/vapor/consensus/segwit" |
9 | 12 | dbm "github.com/bytom/vapor/database/leveldb" |
10 | 13 | "github.com/bytom/vapor/errors" |
@@ -12,20 +15,19 @@ import ( | ||
12 | 15 | "github.com/bytom/vapor/protocol/bc/types" |
13 | 16 | ) |
14 | 17 | |
15 | -const maxFeeRate = 0.05 | |
16 | - | |
17 | 18 | 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") | |
29 | 31 | ) |
30 | 32 | |
31 | 33 | // 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 { | ||
76 | 78 | } |
77 | 79 | |
78 | 80 | // 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) { | |
80 | 82 | if blockHeight <= m.startBlockHeight { |
81 | 83 | return nil, nil |
82 | 84 | } |
@@ -86,7 +88,13 @@ func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, block | ||
86 | 88 | return nil, err |
87 | 89 | } |
88 | 90 | |
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) | |
90 | 98 | tradePairIterator := database.NewTradePairIterator(m.movStore) |
91 | 99 | matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout) |
92 | 100 | return matchCollector.result() |
@@ -140,13 +148,8 @@ func (m *MovCore) StartHeight() uint64 { | ||
140 | 148 | // ValidateBlock no need to verify the block header, because the first module has been verified. |
141 | 149 | // just need to verify the transactions in the block. |
142 | 150 | 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 { | |
150 | 153 | return err |
151 | 154 | } |
152 | 155 | } |
@@ -154,9 +157,9 @@ func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResul | ||
154 | 157 | } |
155 | 158 | |
156 | 159 | // 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 { | |
158 | 161 | if common.IsMatchedTx(tx) { |
159 | - if err := validateMatchedTx(tx, verifyResult); err != nil { | |
162 | + if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil { | |
160 | 163 | return err |
161 | 164 | } |
162 | 165 | } else if common.IsCancelOrderTx(tx) { |
@@ -180,6 +183,41 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro | ||
180 | 183 | return nil |
181 | 184 | } |
182 | 185 | |
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 | + | |
183 | 221 | func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error { |
184 | 222 | if verifyResult.StatusFail { |
185 | 223 | return errStatusFailMustFalse |
@@ -211,13 +249,13 @@ func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte | ||
211 | 249 | return errRatioOfTradeLessThanZero |
212 | 250 | } |
213 | 251 | |
214 | - if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 { | |
252 | + if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 { | |
215 | 253 | return errRequestAmountMath |
216 | 254 | } |
217 | 255 | return nil |
218 | 256 | } |
219 | 257 | |
220 | -func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error { | |
258 | +func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error { | |
221 | 259 | if verifyResult.StatusFail { |
222 | 260 | return errStatusFailMustFalse |
223 | 261 | } |
@@ -246,21 +284,34 @@ func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error { | ||
246 | 284 | return errAssetIDMustUniqueInMatchedTx |
247 | 285 | } |
248 | 286 | |
249 | - return validateMatchedTxFeeAmount(tx) | |
287 | + return validateMatchedTxFee(tx, blockHeight) | |
250 | 288 | } |
251 | 289 | |
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) | |
254 | 292 | if err != nil { |
255 | 293 | return err |
256 | 294 | } |
257 | 295 | |
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 | |
261 | 299 | } |
262 | 300 | } |
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) | |
264 | 315 | } |
265 | 316 | |
266 | 317 | func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error { |
@@ -441,3 +492,31 @@ func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*commo | ||
441 | 492 | } |
442 | 493 | return addOrders, deleteOrders |
443 | 494 | } |
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 | +} |
@@ -1,12 +1,14 @@ | ||
1 | 1 | package mov |
2 | 2 | |
3 | 3 | import ( |
4 | + "encoding/hex" | |
4 | 5 | "math" |
5 | 6 | "os" |
6 | 7 | "testing" |
7 | 8 | |
8 | 9 | "github.com/bytom/vapor/application/mov/common" |
9 | 10 | "github.com/bytom/vapor/application/mov/database" |
11 | + "github.com/bytom/vapor/application/mov/match" | |
10 | 12 | "github.com/bytom/vapor/application/mov/mock" |
11 | 13 | "github.com/bytom/vapor/consensus" |
12 | 14 | dbm "github.com/bytom/vapor/database/leveldb" |
@@ -287,6 +289,14 @@ func TestApplyBlock(t *testing.T) { | ||
287 | 289 | } |
288 | 290 | |
289 | 291 | 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 | + | |
290 | 300 | cases := []struct { |
291 | 301 | desc string |
292 | 302 | block *types.Block |
@@ -362,9 +372,11 @@ func TestValidateBlock(t *testing.T) { | ||
362 | 372 | 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), |
363 | 373 | }, |
364 | 374 | 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), | |
368 | 380 | }, |
369 | 381 | }), |
370 | 382 | }, |
@@ -383,9 +395,10 @@ func TestValidateBlock(t *testing.T) { | ||
383 | 395 | types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}), |
384 | 396 | }, |
385 | 397 | 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), | |
389 | 402 | types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}), |
390 | 403 | }, |
391 | 404 | }), |
@@ -405,10 +418,11 @@ func TestValidateBlock(t *testing.T) { | ||
405 | 418 | 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), |
406 | 419 | }, |
407 | 420 | 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")), | |
412 | 426 | }, |
413 | 427 | }), |
414 | 428 | }, |
@@ -427,7 +441,7 @@ func TestValidateBlock(t *testing.T) { | ||
427 | 441 | }, |
428 | 442 | Outputs: []*types.TxOutput{ |
429 | 443 | 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), | |
431 | 445 | }, |
432 | 446 | }), |
433 | 447 | }, |
@@ -445,18 +459,19 @@ func TestValidateBlock(t *testing.T) { | ||
445 | 459 | 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), |
446 | 460 | }, |
447 | 461 | 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")), | |
450 | 464 | // re-order |
451 | 465 | types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram), |
452 | 466 | // 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), | |
454 | 469 | }, |
455 | 470 | }), |
456 | 471 | }, |
457 | 472 | }, |
458 | 473 | verifyResults: []*bc.TxVerifyResult{{StatusFail: false}}, |
459 | - wantError: errAmountOfFeeGreaterThanMaximum, | |
474 | + wantError: match.ErrAmountOfFeeOutOfRange, | |
460 | 475 | }, |
461 | 476 | { |
462 | 477 | desc: "ratio numerator is zero", |
@@ -507,7 +522,63 @@ func TestValidateBlock(t *testing.T) { | ||
507 | 522 | } |
508 | 523 | } |
509 | 524 | |
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 | + | |
510 | 573 | 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 | + | |
511 | 582 | cases := []struct { |
512 | 583 | desc string |
513 | 584 | initOrders []*common.Order |
@@ -572,7 +643,7 @@ func TestBeforeProposalBlock(t *testing.T) { | ||
572 | 643 | } |
573 | 644 | |
574 | 645 | 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 }) | |
576 | 647 | if err != nil { |
577 | 648 | t.Fatal(err) |
578 | 649 | } |
@@ -75,6 +75,13 @@ type ProducerSubsidy struct { | ||
75 | 75 | Subsidy uint64 |
76 | 76 | } |
77 | 77 | |
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 | + | |
78 | 85 | // Params store the config for different network |
79 | 86 | type Params struct { |
80 | 87 | // Name defines a human-readable identifier for the network. |
@@ -103,7 +110,12 @@ type Params struct { | ||
103 | 110 | ProducerSubsidys []ProducerSubsidy |
104 | 111 | |
105 | 112 | SoftForkPoint map[uint64]uint64 |
113 | + | |
114 | + // Mov will only start when the block height is greater than this value | |
106 | 115 | MovStartHeight uint64 |
116 | + | |
117 | + // Used to receive rewards for matching transactions | |
118 | + MovRewardPrograms []MovRewardProgram | |
107 | 119 | } |
108 | 120 | |
109 | 121 | // ActiveNetParams is the active NetParams |
@@ -145,7 +157,8 @@ var MainNetParams = Params{ | ||
145 | 157 | ProducerSubsidys: []ProducerSubsidy{ |
146 | 158 | {BeginBlock: 1, EndBlock: 63072000, Subsidy: 9512938}, |
147 | 159 | }, |
148 | - SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600}, | |
160 | + SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600}, | |
161 | + MovStartHeight: 42000000, | |
149 | 162 | } |
150 | 163 | |
151 | 164 | // TestNetParams is the config for vapor-testnet |
@@ -102,7 +102,7 @@ func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) e | ||
102 | 102 | continue |
103 | 103 | } |
104 | 104 | |
105 | - results, gasLeft := preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft) | |
105 | + results, gasLeft := b.preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft) | |
106 | 106 | for _, result := range results { |
107 | 107 | if result.err != nil && !result.gasOnly { |
108 | 108 | 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 { | ||
139 | 139 | } |
140 | 140 | |
141 | 141 | func (b *blockBuilder) applyTransactionFromSubProtocol() error { |
142 | - cp, err := b.accountManager.GetCoinbaseControlProgram() | |
143 | - if err != nil { | |
144 | - return err | |
145 | - } | |
146 | - | |
147 | 142 | isTimeout := func() bool { |
148 | 143 | return b.getTimeoutStatus() > timeoutOk |
149 | 144 | } |
@@ -153,7 +148,7 @@ func (b *blockBuilder) applyTransactionFromSubProtocol() error { | ||
153 | 148 | break |
154 | 149 | } |
155 | 150 | |
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) | |
157 | 152 | if err != nil { |
158 | 153 | log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package") |
159 | 154 | continue |
@@ -294,7 +289,7 @@ type validateTxResult struct { | ||
294 | 289 | err error |
295 | 290 | } |
296 | 291 | |
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) { | |
298 | 293 | var results []*validateTxResult |
299 | 294 | bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}} |
300 | 295 | bcTxs := make([]*bc.Tx, len(txs)) |
@@ -328,7 +323,7 @@ func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoView | ||
328 | 323 | continue |
329 | 324 | } |
330 | 325 | |
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 { | |
332 | 327 | results = append(results, &validateTxResult{tx: txs[i], err: err}) |
333 | 328 | continue |
334 | 329 | } |
@@ -339,10 +334,10 @@ func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoView | ||
339 | 334 | return results, gasLeft |
340 | 335 | } |
341 | 336 | |
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 { | |
343 | 338 | for _, subProtocol := range subProtocols { |
344 | 339 | 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 { | |
346 | 341 | return err |
347 | 342 | } |
348 | 343 | } |
@@ -142,8 +142,13 @@ func (c *Chain) validateSign(block *types.Block) error { | ||
142 | 142 | |
143 | 143 | if err := c.checkNodeSign(&block.BlockHeader, node, block.Get(node.Order)); err == errDoubleSignBlock { |
144 | 144 | 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 | + } | |
147 | 152 | } else if err != nil { |
148 | 153 | return err |
149 | 154 | } |
@@ -22,11 +22,10 @@ const ( | ||
22 | 22 | type Protocoler interface { |
23 | 23 | Name() string |
24 | 24 | 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) | |
26 | 26 | ChainStatus() (uint64, *bc.Hash, error) |
27 | 27 | 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 | |
30 | 29 | ApplyBlock(block *types.Block) error |
31 | 30 | DetachBlock(block *types.Block) error |
32 | 31 | } |
@@ -54,7 +54,7 @@ func (c *Chain) validateTx(tx *types.Tx, bh *types.BlockHeader) (bool, error) { | ||
54 | 54 | |
55 | 55 | txVerifyResult := &bc.TxVerifyResult{StatusFail: err != nil} |
56 | 56 | 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 { | |
58 | 58 | c.txPool.AddErrCache(&tx.ID, err) |
59 | 59 | return false, err |
60 | 60 | } |
@@ -101,6 +101,13 @@ func ValidateBlock(b *bc.Block, parent *types.BlockHeader, rewards []state.Coinb | ||
101 | 101 | return errors.Wrapf(validateResult.err, "validate of transaction %d of %d", i, len(b.Transactions)) |
102 | 102 | } |
103 | 103 | |
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 | + | |
104 | 111 | if err := b.TransactionStatus.SetStatus(i, validateResult.err != nil); err != nil { |
105 | 112 | return err |
106 | 113 | } |
@@ -922,8 +922,10 @@ func TestMagneticContractTx(t *testing.T) { | ||
922 | 922 | types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer), |
923 | 923 | }, |
924 | 924 | 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}), | |
927 | 929 | }, |
928 | 930 | }), |
929 | 931 | }, |
@@ -942,9 +944,11 @@ func TestMagneticContractTx(t *testing.T) { | ||
942 | 944 | types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer), |
943 | 945 | }, |
944 | 946 | Outputs: []*types.TxOutput{ |
945 | - types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram), | |
947 | + types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram), | |
946 | 948 | 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}), | |
948 | 952 | }, |
949 | 953 | }), |
950 | 954 | }, |
@@ -963,9 +967,11 @@ func TestMagneticContractTx(t *testing.T) { | ||
963 | 967 | types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer), |
964 | 968 | }, |
965 | 969 | 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), | |
968 | 972 | types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer), |
973 | + types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}), | |
974 | + types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}), | |
969 | 975 | }, |
970 | 976 | }), |
971 | 977 | }, |
@@ -1063,9 +1069,11 @@ func TestMagneticContractTx(t *testing.T) { | ||
1063 | 1069 | types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer), |
1064 | 1070 | }, |
1065 | 1071 | Outputs: []*types.TxOutput{ |
1066 | - types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram), | |
1072 | + types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram), | |
1067 | 1073 | 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}), | |
1069 | 1077 | }, |
1070 | 1078 | }), |
1071 | 1079 | }, |
@@ -1139,9 +1147,12 @@ func TestRingMagneticContractTx(t *testing.T) { | ||
1139 | 1147 | types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram), |
1140 | 1148 | }, |
1141 | 1149 | 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}), | |
1145 | 1156 | }, |
1146 | 1157 | }), |
1147 | 1158 | }, |
@@ -166,14 +166,16 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) { | ||
166 | 166 | // standardProgram: Program, |
167 | 167 | // sellerKey: PublicKey) locks valueAmount of valueAsset { |
168 | 168 | // 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 | |
172 | 173 | // lock valueAmount-actualAmount of valueAsset with standardProgram |
173 | 174 | // unlock actualAmount of valueAsset |
174 | 175 | // } |
175 | 176 | // clause fullTrade() { |
176 | 177 | // define requestedAmount: Integer = valueAmount * ratioNumerator / ratioDenominator |
178 | +// define requestedAmount: Integer = requestedAmount * 999 / 1000 | |
177 | 179 | // verify requestedAmount > 0 |
178 | 180 | // lock requestedAmount of requestedAsset with sellerProgram |
179 | 181 | // unlock valueAmount of valueAsset |
@@ -219,12 +221,15 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) { | ||
219 | 221 | // TOALTSTACK [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>] |
220 | 222 | // 6 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> 6] |
221 | 223 | // 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)] | |
228 | 233 | // VERIFY [... sellerKey standardProgram actualAmount] |
229 | 234 | // FROMALTSTACK [... sellerKey standardProgram actualAmount <position>] |
230 | 235 | // 1 [... sellerKey standardProgram actualAmount <position> 1] |
@@ -246,6 +251,9 @@ func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) { | ||
246 | 251 | // 3 [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioNumerator 3] |
247 | 252 | // ROLL [... sellerKey standardProgram sellerProgram requestedAsset valueAmount ratioNumerator ratioDenominator] |
248 | 253 | // 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] | |
249 | 257 | // DUP [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount] |
250 | 258 | // 0 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount 0] |
251 | 259 | // GREATERTHAN [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)] |
@@ -315,6 +323,9 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) { | ||
315 | 323 | builder.AddOp(vm.OP_TOALTSTACK) |
316 | 324 | builder.AddOp(vm.OP_6) |
317 | 325 | builder.AddOp(vm.OP_ROLL) |
326 | + builder.AddInt64(999) | |
327 | + builder.AddInt64(1000) | |
328 | + builder.AddOp(vm.OP_MULFRACTION) | |
318 | 329 | builder.AddOp(vm.OP_3) |
319 | 330 | builder.AddOp(vm.OP_ROLL) |
320 | 331 | builder.AddOp(vm.OP_1) |
@@ -340,6 +351,9 @@ func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) { | ||
340 | 351 | builder.AddOp(vm.OP_3) |
341 | 352 | builder.AddOp(vm.OP_ROLL) |
342 | 353 | builder.AddOp(vm.OP_MULFRACTION) |
354 | + builder.AddInt64(999) | |
355 | + builder.AddInt64(1000) | |
356 | + builder.AddOp(vm.OP_MULFRACTION) | |
343 | 357 | builder.AddOp(vm.OP_DUP) |
344 | 358 | builder.AddOp(vm.OP_0) |
345 | 359 | builder.AddOp(vm.OP_GREATERTHAN) |