7 "github.com/vapor/application/mov/common"
8 "github.com/vapor/application/mov/database"
9 "github.com/vapor/application/mov/util"
10 "github.com/vapor/consensus/segwit"
11 "github.com/vapor/errors"
12 vprMath "github.com/vapor/math"
13 "github.com/vapor/protocol/bc"
14 "github.com/vapor/protocol/bc/types"
15 "github.com/vapor/protocol/vm"
16 "github.com/vapor/protocol/vm/vmutil"
22 orderTable *OrderTable
26 func NewEngine(movStore database.MovStore, nodeProgram []byte) *Engine {
27 return &Engine{orderTable: NewOrderTable(movStore), nodeProgram: nodeProgram}
30 // NextMatchedTx return the next matchable transaction by the specified trade pairs
31 // the size of trade pairs at least, and the sequence of trade pairs can form a loop
32 // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
33 func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
34 if err := validateTradePairs(tradePairs); err != nil {
38 var orders []*common.Order
39 for _, tradePair := range tradePairs {
40 order := e.orderTable.PeekOrder(tradePair)
45 orders = append(orders, order)
48 tx, err := e.buildMatchTx(orders)
57 for _, tradePair := range tradePairs {
58 e.orderTable.PopOrder(tradePair)
60 if err := addPartialTradeOrder(tx, e.orderTable); err != nil {
66 func validateTradePairs(tradePairs []*common.TradePair) error {
67 if len(tradePairs) < 2 {
68 return errors.New("size of trade pairs at least 2")
71 for i, tradePair:= range tradePairs {
72 oppositeTradePair := tradePairs[getOppositeIndex(len(tradePairs), i)]
73 if *tradePair.FromAssetID != *oppositeTradePair.ToAssetID || *tradePair.ToAssetID != *oppositeTradePair.FromAssetID {
74 return errors.New("specified trade pairs is invalid")
80 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
81 txData := &types.TxData{Version: 1}
82 var partialTradeStatus []bool
83 var receiveAmounts []uint64
85 for i, order := range orders {
86 contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
91 oppositeOrder := orders[getOppositeIndex(len(orders), i)]
92 oppositeContractArgs, err := segwit.DecodeP2WMCProgram(oppositeOrder.Utxo.ControlProgram)
97 if canNotBeMatched(contractArgs, oppositeContractArgs) {
101 txData.Inputs = append(txData.Inputs, types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram))
102 isPartialTrade, receiveAmount := addMatchTxOutput(txData, order, contractArgs, oppositeOrder.Utxo.Amount)
103 partialTradeStatus = append(partialTradeStatus, isPartialTrade)
104 receiveAmounts = append(receiveAmounts, receiveAmount)
107 setMatchTxArguments(txData, partialTradeStatus, receiveAmounts)
108 if err := e.addMatchTxFeeOutput(txData); err != nil {
112 byteData, err := txData.MarshalText()
117 txData.SerializedSize = uint64(len(byteData))
118 tx := types.NewTx(*txData)
122 func canNotBeMatched(contractArgs, oppositeContractArgs *vmutil.MagneticContractArgs) bool {
123 if contractArgs.RatioNumerator == 0 || oppositeContractArgs.RatioDenominator == 0 {
127 buyRate := big.NewFloat(0).Quo(big.NewFloat(0).SetInt64(contractArgs.RatioDenominator), big.NewFloat(0).SetInt64(contractArgs.RatioNumerator))
128 sellRate := big.NewFloat(0).Quo(big.NewFloat(0).SetInt64(oppositeContractArgs.RatioNumerator), big.NewFloat(0).SetInt64(oppositeContractArgs.RatioDenominator))
129 return buyRate.Cmp(sellRate) < 0
132 // addMatchTxOutput return whether partial matched
133 func addMatchTxOutput(txData *types.TxData, order *common.Order, contractArgs *vmutil.MagneticContractArgs, oppositeAmount uint64) (bool, uint64) {
134 requestAmount := calcRequestAmount(order.Utxo.Amount, contractArgs)
135 receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
136 shouldPayAmount := CalcShouldPayAmount(receiveAmount, contractArgs)
138 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
139 if order.Utxo.Amount > shouldPayAmount {
140 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
141 return true, receiveAmount
143 return false, receiveAmount
146 func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
147 feeAssetAmountMap, err := CalcFeeFromMatchedTx(txData)
152 for feeAssetID, amount := range feeAssetAmountMap {
153 var reminder uint64 = 0
154 feeAmount := amount.payableFeeAmount
155 if amount.payableFeeAmount > amount.maxFeeAmount {
156 feeAmount = amount.maxFeeAmount
157 reminder = amount.payableFeeAmount - amount.maxFeeAmount
159 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, feeAmount, e.nodeProgram))
161 // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
162 averageAmount := reminder / uint64(len(txData.Inputs))
163 if averageAmount == 0 {
166 for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
167 contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
172 if i == len(txData.Inputs)-1 {
173 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, reminder, contractArgs.SellerProgram))
175 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, averageAmount, contractArgs.SellerProgram))
177 reminder -= averageAmount
183 func setMatchTxArguments(txData *types.TxData, partialTradeStatus []bool, receiveAmounts []uint64) {
185 for i, isPartial := range partialTradeStatus {
186 var arguments [][]byte
188 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts[i])), vm.Int64Bytes(position), vm.Int64Bytes(0)}
191 arguments = [][]byte{vm.Int64Bytes(position), vm.Int64Bytes(1)}
194 txData.Inputs[i].SetArguments(arguments)
198 func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error {
199 for i, output := range tx.Outputs {
200 if !segwit.IsP2WMCScript(output.ControlProgram()) {
204 order, err := common.NewOrderFromOutput(tx, i)
209 if err := orderTable.AddOrder(order); err != nil {
216 func getOppositeIndex(size int, selfIdx int) int {
217 oppositeIdx := selfIdx + 1
218 if selfIdx >= size - 1 {
224 func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
225 return uint64(int64(fromAmount) * contractArg.RatioNumerator / contractArg.RatioDenominator)
228 func CalcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
229 return uint64(math.Ceil(float64(receiveAmount) * float64(contractArg.RatioDenominator) / float64(contractArg.RatioNumerator)))
232 func CalcMaxFeeAmount(shouldPayAmount uint64) uint64 {
233 return uint64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
236 type feeAmount struct {
238 payableFeeAmount uint64
241 func CalcFeeFromMatchedTx(txData *types.TxData) (map[bc.AssetID]*feeAmount, error) {
242 assetAmountMap := make(map[bc.AssetID]*feeAmount)
243 for _, input := range txData.Inputs {
244 assetAmountMap[input.AssetID()] = &feeAmount{}
247 for _, input := range txData.Inputs {
248 assetAmountMap[input.AssetID()].payableFeeAmount += input.AssetAmount().Amount
249 outputPos, err := util.GetTradeReceivePosition(input)
254 receiveOutput := txData.Outputs[outputPos]
255 assetAmountMap[*receiveOutput.AssetAmount().AssetId].payableFeeAmount -= receiveOutput.AssetAmount().Amount
256 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
261 assetAmountMap[input.AssetID()].maxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveOutput.AssetAmount().Amount, contractArgs))
264 for _, output := range txData.Outputs {
265 // minus the amount of the re-order
266 if segwit.IsP2WMCScript(output.ControlProgram()) {
267 assetAmountMap[*output.AssetAmount().AssetId].payableFeeAmount -= output.AssetAmount().Amount
271 for assetID, amount := range assetAmountMap {
272 if amount.payableFeeAmount == 0 {
273 delete(assetAmountMap, assetID)
276 return assetAmountMap, nil