7 "github.com/vapor/application/mov/common"
8 "github.com/vapor/application/mov/contract"
9 "github.com/vapor/consensus/segwit"
10 "github.com/vapor/errors"
11 vprMath "github.com/vapor/math"
12 "github.com/vapor/protocol/bc"
13 "github.com/vapor/protocol/bc/types"
14 "github.com/vapor/protocol/vm"
15 "github.com/vapor/protocol/vm/vmutil"
19 orderTable *OrderTable
24 func NewEngine(orderTable *OrderTable, maxFeeRate float64, nodeProgram []byte) *Engine {
25 return &Engine{orderTable: orderTable, maxFeeRate: maxFeeRate, nodeProgram: nodeProgram}
28 func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
29 if err := validateTradePairs(tradePairs); err != nil {
33 orders := e.peekOrders(tradePairs)
38 return isMatched(orders)
41 // NextMatchedTx return the next matchable transaction by the specified trade pairs
42 // the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
43 // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
44 func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
45 if err := validateTradePairs(tradePairs); err != nil {
49 orders := e.peekOrders(tradePairs)
51 return nil, errors.New("no order for the specified trade pair in the order table")
54 if !isMatched(orders) {
55 return nil, errors.New("the specified trade pairs can not be matched")
58 tx, err := e.buildMatchTx(orders)
63 for _, tradePair := range tradePairs {
64 e.orderTable.PopOrder(tradePair)
67 if err := addPartialTradeOrder(tx, e.orderTable); err != nil {
73 func (e *Engine) peekOrders(tradePairs []*common.TradePair) []*common.Order {
74 var orders []*common.Order
75 for _, tradePair := range tradePairs {
76 order := e.orderTable.PeekOrder(tradePair)
81 orders = append(orders, order)
86 func validateTradePairs(tradePairs []*common.TradePair) error {
87 if len(tradePairs) < 2 {
88 return errors.New("size of trade pairs at least 2")
91 for i, tradePair := range tradePairs {
92 oppositeTradePair := tradePairs[getOppositeIndex(len(tradePairs), i)]
93 if *tradePair.ToAssetID != *oppositeTradePair.FromAssetID {
94 return errors.New("specified trade pairs is invalid")
100 func isMatched(orders []*common.Order) bool {
101 for i, order := range orders {
102 opposisteOrder := orders[getOppositeIndex(len(orders), i)]
103 if 1 / order.Rate < opposisteOrder.Rate {
110 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
111 txData := &types.TxData{Version: 1}
112 for i, order := range orders {
113 input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
114 txData.Inputs = append(txData.Inputs, input)
116 oppositeOrder := orders[getOppositeIndex(len(orders), i)]
117 if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil {
122 if err := e.addMatchTxFeeOutput(txData); err != nil {
126 byteData, err := txData.MarshalText()
131 txData.SerializedSize = uint64(len(byteData))
132 return types.NewTx(*txData), nil
135 func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error {
136 contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
141 requestAmount := calcRequestAmount(order.Utxo.Amount, contractArgs)
142 receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
143 shouldPayAmount := CalcShouldPayAmount(receiveAmount, contractArgs)
144 isPartialTrade := requestAmount > receiveAmount
146 setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount)
147 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
149 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
154 func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
155 txFee, err := CalcMatchedTxFee(txData, e.maxFeeRate)
160 for feeAssetID, amount := range txFee {
161 var reminder int64 = 0
162 feeAmount := amount.FeeAmount
163 if amount.FeeAmount > amount.MaxFeeAmount {
164 feeAmount = amount.MaxFeeAmount
165 reminder = amount.FeeAmount - amount.MaxFeeAmount
167 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(feeAmount), e.nodeProgram))
169 // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
170 averageAmount := reminder / int64(len(txData.Inputs))
171 if averageAmount == 0 {
174 for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
175 contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
180 if i == len(txData.Inputs)-1 {
181 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(reminder), contractArgs.SellerProgram))
183 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(averageAmount), contractArgs.SellerProgram))
185 reminder -= averageAmount
191 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
192 var arguments [][]byte
194 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
196 arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
198 txInput.SetArguments(arguments)
201 func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error {
202 for i, output := range tx.Outputs {
203 if !segwit.IsP2WMCScript(output.ControlProgram()) {
207 order, err := common.NewOrderFromOutput(tx, i)
212 if err := orderTable.AddOrder(order); err != nil {
219 func getOppositeIndex(size int, selfIdx int) int {
220 oppositeIdx := selfIdx + 1
221 if selfIdx >= size-1 {
227 type MatchedTxFee struct {
232 func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) {
233 assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
234 sellerProgramMap := make(map[string]bool)
235 assetInputMap := make(map[bc.AssetID]uint64)
237 for _, input := range txData.Inputs {
238 assetFeeMap[input.AssetID()] = &MatchedTxFee{}
239 assetFeeMap[input.AssetID()].FeeAmount += int64(input.AssetAmount().Amount)
240 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
245 sellerProgramMap[hex.EncodeToString(contractArgs.SellerProgram)] = true
246 assetInputMap[input.AssetID()] = input.Amount()
249 for _, input := range txData.Inputs {
250 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
255 oppositeAmount := assetInputMap[contractArgs.RequestedAsset]
256 receiveAmount := vprMath.MinUint64(calcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
257 assetFeeMap[input.AssetID()].MaxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate)
260 for _, output := range txData.Outputs {
261 // minus the amount of the re-order
262 if segwit.IsP2WMCScript(output.ControlProgram()) {
263 assetFeeMap[*output.AssetAmount().AssetId].FeeAmount -= int64(output.AssetAmount().Amount)
265 // minus the amount of seller's receiving output
266 if _, ok := sellerProgramMap[hex.EncodeToString(output.ControlProgram())]; ok {
267 assetID := *output.AssetAmount().AssetId
268 fee, ok := assetFeeMap[assetID]
272 fee.FeeAmount -= int64(output.AssetAmount().Amount)
273 if fee.FeeAmount == 0 {
274 delete(assetFeeMap, assetID)
278 return assetFeeMap, nil
281 func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
282 return uint64(int64(fromAmount) * contractArg.RatioNumerator / contractArg.RatioDenominator)
285 func CalcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
286 return uint64(math.Floor(float64(receiveAmount) * float64(contractArg.RatioDenominator) / float64(contractArg.RatioNumerator)))
289 func CalcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
290 return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))