return tx, nil
}
-func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, refundAmounts, feeAmounts []*bc.AssetAmount) error {
- for _, feeAmount := range feeAmounts {
+func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, refunds []RefundAssets, fees []*bc.AssetAmount) error {
+ for _, feeAmount := range fees {
txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
}
- for i, refundAmount := range refundAmounts {
- contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
- if err != nil {
- return err
- }
+ for i, refund := range refunds {
+ // each trading participant may be refunded multiple assets
+ for _, assetAmount := range refund {
+ contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
+ if err != nil {
+ return err
+ }
- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*refundAmount.AssetId, refundAmount.Amount, contractArgs.SellerProgram))
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, contractArgs.SellerProgram))
+ }
}
return nil
}
txData.Inputs = append(txData.Inputs, input)
}
- receivedAmounts, priceDiff := CalcReceivedAmount(orders)
- receivedAfterDeductFee, refundAmounts, feeAmounts := e.feeStrategy.Allocate(receivedAmounts, priceDiff)
- if err := addMatchTxOutput(txData, orders, receivedAmounts, receivedAfterDeductFee); err != nil {
+ receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
+ allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
+ if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets.Received); err != nil {
return nil, err
}
- if err := e.addMatchTxFeeOutput(txData, refundAmounts, feeAmounts); err != nil {
+ if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Refunds, allocatedAssets.Fees); err != nil {
return nil, err
}
}
// CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
-func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, *bc.AssetAmount) {
- priceDiff := &bc.AssetAmount{}
- if len(orders) == 0 {
- return nil, priceDiff
- }
-
+func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, map[bc.AssetID]int64) {
+ priceDiffs := make(map[bc.AssetID]int64)
var receivedAmounts, shouldPayAmounts []*bc.AssetAmount
for i, order := range orders {
requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
for i, receivedAmount := range receivedAmounts {
oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
- priceDiff.AssetId = oppositeShouldPayAmount.AssetId
- priceDiff.Amount = oppositeShouldPayAmount.Amount - receivedAmount.Amount
- // price differential can only produce once
- break
+ assetId := oppositeShouldPayAmount.AssetId
+ amount := oppositeShouldPayAmount.Amount - receivedAmount.Amount
+ priceDiffs[*assetId] = int64(amount)
}
}
- return receivedAmounts, priceDiff
+ return receivedAmounts, priceDiffs
}
// IsMatched check does the orders can be exchange
var (
// ErrAmountOfFeeExceedMaximum represent The fee charged is exceeded the maximum
ErrAmountOfFeeExceedMaximum = errors.New("amount of fee greater than max fee amount")
- // ErrFeeMoreThanOneAsset represent the fee charged can only have one asset
- ErrFeeMoreThanOneAsset = errors.New("fee can only be an asset")
)
+// AllocatedAssets represent reallocated assets after calculating fees
+type AllocatedAssets struct {
+ Received []*bc.AssetAmount
+ Refunds []RefundAssets
+ Fees []*bc.AssetAmount
+}
+
+// RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
+type RefundAssets []*bc.AssetAmount
+
// FeeStrategy used to indicate how to charge a matching fee
type FeeStrategy interface {
// Allocate will allocate the price differential in matching transaction to the participants and the fee
// @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
- // @param priceDiff price differential of matching transaction
- // @return the amount of assets that the participants in the matching transaction can received when fee is considered
- // @return the amount of assets returned to the transaction participant when the fee exceeds a certain ratio
- // @return the amount of fees
- Allocate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount) ([]*bc.AssetAmount, []*bc.AssetAmount, []*bc.AssetAmount)
+ // @param priceDiffs price differential of matching transaction
+ // @return reallocated assets after calculating fees
+ Allocate(receiveAmounts []*bc.AssetAmount, priceDiffs map[bc.AssetID]int64) *AllocatedAssets
// Validate verify that the fee charged for a matching transaction is correct
- Validate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount, feeAmounts map[bc.AssetID]int64) error
+ Validate(receiveAmounts []*bc.AssetAmount, priceDiffs, feeAmounts map[bc.AssetID]int64) error
}
// DefaultFeeStrategy represent the default fee charge strategy
}
// Allocate will allocate the price differential in matching transaction to the participants and the fee
-func (d *DefaultFeeStrategy) Allocate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount) ([]*bc.AssetAmount, []*bc.AssetAmount, []*bc.AssetAmount) {
- receivedAfterDeductFee := make([]*bc.AssetAmount, len(receiveAmounts))
- copy(receivedAfterDeductFee, receiveAmounts)
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts []*bc.AssetAmount, priceDiffs map[bc.AssetID]int64) *AllocatedAssets {
+ var feeAmounts []*bc.AssetAmount
+ refundAmounts := make([]RefundAssets, len(receiveAmounts))
- if priceDiff.Amount == 0 {
- return receivedAfterDeductFee, nil, nil
- }
-
- var maxFeeAmount int64
for _, receiveAmount := range receiveAmounts {
- if *receiveAmount.AssetId == *priceDiff.AssetId {
- maxFeeAmount = calcMaxFeeAmount(receiveAmount.Amount, d.maxFeeRate)
+ if _, ok := priceDiffs[*receiveAmount.AssetId]; !ok {
+ continue
}
- }
- priceDiffAmount := int64(priceDiff.Amount)
- feeAmount, reminder := priceDiffAmount, int64(0)
- if priceDiffAmount > maxFeeAmount {
- feeAmount = maxFeeAmount
- reminder = priceDiffAmount - maxFeeAmount
- }
+ priceDiff := priceDiffs[*receiveAmount.AssetId]
+ maxFeeAmount := calcMaxFeeAmount(receiveAmount.Amount, d.maxFeeRate)
- // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
- averageAmount := reminder / int64(len(receiveAmounts))
- if averageAmount == 0 {
- averageAmount = 1
- }
+ feeAmount, reminder := priceDiff, int64(0)
+ if priceDiff > maxFeeAmount {
+ feeAmount = maxFeeAmount
+ reminder = priceDiff - maxFeeAmount
+ }
+
+ feeAmounts = append(feeAmounts, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: uint64(feeAmount)})
- var refundAmounts []*bc.AssetAmount
- for i := 0; i < len(receiveAmounts) && reminder > 0; i++ {
- amount := averageAmount
- if i == len(receiveAmounts)-1 {
- amount = reminder
+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
+ averageAmount := reminder / int64(len(receiveAmounts))
+ if averageAmount == 0 {
+ averageAmount = 1
+ }
+
+ for i := 0; i < len(receiveAmounts) && reminder > 0; i++ {
+ amount := averageAmount
+ if i == len(receiveAmounts)-1 {
+ amount = reminder
+ }
+ refundAmounts[i] = append(refundAmounts[i], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: uint64(amount)})
+ reminder -= averageAmount
}
- refundAmounts = append(refundAmounts, &bc.AssetAmount{AssetId: priceDiff.AssetId, Amount: uint64(amount)})
- reminder -= averageAmount
}
- feeAmounts := []*bc.AssetAmount{{AssetId: priceDiff.AssetId, Amount: uint64(feeAmount)}}
- return receivedAfterDeductFee, refundAmounts, feeAmounts
+ receivedAfterDeductFee := make([]*bc.AssetAmount, len(receiveAmounts))
+ copy(receivedAfterDeductFee, receiveAmounts)
+ return &AllocatedAssets{Received: receivedAfterDeductFee, Refunds: refundAmounts, Fees: feeAmounts}
}
// Validate verify that the fee charged for a matching transaction is correct
-func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount, feeAmounts map[bc.AssetID]int64) error {
- if len(feeAmounts) > 1 {
- return ErrFeeMoreThanOneAsset
- }
-
+func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts, priceDiffs map[bc.AssetID]int64) error {
for _, receiveAmount := range receiveAmounts {
if feeAmount, ok := feeAmounts[*receiveAmount.AssetId]; ok {
- if feeAmount > calcMaxFeeAmount(receiveAmount.Amount, d.maxFeeRate) {
+ if feeAmount > calcMaxFeeAmount(receiveAmount.Amount+uint64(priceDiffs[*receiveAmount.AssetId]), d.maxFeeRate) {
return ErrAmountOfFeeExceedMaximum
}
}
mock.MatchedTxs[6],
},
},
+ {
+ desc: "multiple assets as a fee",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[3],
+ },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[11],
+ },
+ },
}
for i, c := range cases {
ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
},
},
+ {
+ FromAssetID: Ð,
+ ToAssetID: &BTC,
+ RatioNumerator: 1,
+ RatioDenominator: 150,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("82752cda63c877a8529d7a7461da6096673e45b3e0b019ce44aa18687ad20445")),
+ SourcePos: 0,
+ Amount: 600,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"), 1, 150.0),
+ },
+ },
}
Eos2EtcOrders = []*common.Order{
types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, RewardProgram),
},
}),
+
+ // full matched transaction from Btc2EthOrders[0] Eth2BtcOrders[3]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ 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),
+ 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),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 4, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 25, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 1, RewardProgram),
+ // refund
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 37, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 2, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ },
+ }),
}
)
feeAmounts[assetID] = fee.amount
}
- receivedAmount, priceDiff := match.CalcReceivedAmount(orders)
+ receivedAmount, priceDiffs := match.CalcReceivedAmount(orders)
feeStrategy := match.NewDefaultFeeStrategy(maxFeeRate)
- return feeStrategy.Validate(receivedAmount, priceDiff, feeAmounts)
+ return feeStrategy.Validate(receivedAmount, feeAmounts, priceDiffs)
}
func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {