6 "github.com/bytom/vapor/application/mov/common"
7 "github.com/bytom/vapor/application/mov/contract"
8 "github.com/bytom/vapor/consensus/segwit"
9 "github.com/bytom/vapor/errors"
10 vprMath "github.com/bytom/vapor/math"
11 "github.com/bytom/vapor/protocol/bc"
12 "github.com/bytom/vapor/protocol/bc/types"
13 "github.com/bytom/vapor/protocol/vm"
16 // Engine is used to generate math transactions
19 feeStrategy FeeStrategy
23 // NewEngine return a new Engine
24 func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
25 return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
28 // HasMatchedTx check does the input trade pair can generate a match deal
29 func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
30 if err := validateTradePairs(tradePairs); err != nil {
34 orders := e.orderBook.PeekOrders(tradePairs)
39 return IsMatched(orders)
42 // NextMatchedTx return the next matchable transaction by the specified trade pairs
43 // the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
44 // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
45 func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
46 if !e.HasMatchedTx(tradePairs...) {
47 return nil, errors.New("the specified trade pairs can not be matched")
50 tx, err := e.buildMatchTx(sortOrders(e.orderBook.PeekOrders(tradePairs)))
55 for _, tradePair := range tradePairs {
56 e.orderBook.PopOrder(tradePair)
59 if err := e.addPartialTradeOrder(tx); err != nil {
65 func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, fees []*bc.AssetAmount) error {
66 for _, feeAmount := range fees {
67 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
70 refoundAmount := map[bc.AssetID]uint64{}
71 assetIDs := []bc.AssetID{}
72 refoundScript := [][]byte{}
73 for _, input := range txData.Inputs {
74 refoundAmount[input.AssetID()] += input.Amount()
75 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
80 assetIDs = append(assetIDs, input.AssetID())
81 refoundScript = append(refoundScript, contractArgs.SellerProgram)
84 for _, output := range txData.Outputs {
85 assetAmount := output.AssetAmount()
86 refoundAmount[*assetAmount.AssetId] -= assetAmount.Amount
89 refoundCount := len(refoundScript)
90 for _, assetID := range assetIDs {
91 amount := refoundAmount[assetID]
92 averageAmount := amount / uint64(refoundCount)
93 if averageAmount == 0 {
97 for i := 0; i < refoundCount && amount > 0; i++ {
98 if i == refoundCount-1 {
99 averageAmount = amount
101 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, averageAmount, refoundScript[i]))
102 amount -= averageAmount
108 func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
109 for i, output := range tx.Outputs {
110 if !segwit.IsP2WMCScript(output.ControlProgram()) || output.AssetAmount().Amount == 0 {
114 order, err := common.NewOrderFromOutput(tx, i)
119 e.orderBook.AddOrder(order)
124 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
125 txData := &types.TxData{Version: 1}
126 for _, order := range orders {
127 input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
128 txData.Inputs = append(txData.Inputs, input)
131 receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
132 allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
133 if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets); err != nil {
137 if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Fees); err != nil {
141 byteData, err := txData.MarshalText()
146 txData.SerializedSize = uint64(len(byteData))
147 return types.NewTx(*txData), nil
150 func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets) error {
151 for i, order := range orders {
152 contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
157 receivedAmount := receivedAmounts[i].Amount
158 shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
160 requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
161 exchangeAmount := order.Utxo.Amount - shouldPayAmount
162 isPartialTrade := requestAmount > receivedAmount && CalcRequestAmount(exchangeAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) >= 1
164 setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmount)
165 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, allocatedAssets.Receives[i].Amount, contractArgs.SellerProgram))
167 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, exchangeAmount, order.Utxo.ControlProgram))
173 func calcOppositeIndex(size int, selfIdx int) int {
174 return (selfIdx + 1) % size
177 // CalcRequestAmount is from amount * numerator / ratioDenominator
178 func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
179 res := big.NewInt(0).SetUint64(fromAmount)
180 res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
187 func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
188 res := big.NewInt(0).SetUint64(receiveAmount)
189 res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
196 // CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
197 func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, []*bc.AssetAmount) {
198 var receivedAmounts, priceDiffs, shouldPayAmounts []*bc.AssetAmount
199 for i, order := range orders {
200 requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
201 oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
202 receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
203 shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
204 receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
205 shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
208 for i, receivedAmount := range receivedAmounts {
209 oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
210 priceDiffs = append(priceDiffs, &bc.AssetAmount{AssetId: oppositeShouldPayAmount.AssetId, Amount: 0})
211 if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
212 priceDiffs[i].Amount = oppositeShouldPayAmount.Amount - receivedAmount.Amount
215 return receivedAmounts, priceDiffs
218 // IsMatched check does the orders can be exchange
219 func IsMatched(orders []*common.Order) bool {
220 sortedOrders := sortOrders(orders)
221 if len(sortedOrders) == 0 {
225 product := big.NewRat(1, 1)
226 for _, order := range orders {
227 product.Mul(product, big.NewRat(order.RatioNumerator, order.RatioDenominator))
229 one := big.NewRat(1, 1)
230 return product.Cmp(one) <= 0
233 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
234 var arguments [][]byte
236 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
238 arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
240 txInput.SetArguments(arguments)
243 func sortOrders(orders []*common.Order) []*common.Order {
244 if len(orders) == 0 {
248 orderMap := make(map[bc.AssetID]*common.Order)
249 firstOrder := orders[0]
250 for i := 1; i < len(orders); i++ {
251 orderMap[*orders[i].FromAssetID] = orders[i]
254 sortedOrders := []*common.Order{firstOrder}
255 for order := firstOrder; *order.ToAssetID != *firstOrder.FromAssetID; {
256 nextOrder, ok := orderMap[*order.ToAssetID]
261 sortedOrders = append(sortedOrders, nextOrder)
267 func validateTradePairs(tradePairs []*common.TradePair) error {
268 if len(tradePairs) < 2 {
269 return errors.New("size of trade pairs at least 2")
272 assetMap := make(map[string]bool)
273 for _, tradePair := range tradePairs {
274 assetMap[tradePair.FromAssetID.String()] = true
275 if *tradePair.FromAssetID == *tradePair.ToAssetID {
276 return errors.New("from asset id can't equal to asset id")
280 for _, tradePair := range tradePairs {
281 key := tradePair.ToAssetID.String()
282 if _, ok := assetMap[key]; !ok {
283 return errors.New("invalid trade pairs")
285 delete(assetMap, key)