import (
"encoding/hex"
+ "github.com/bytom/vapor/errors"
"github.com/bytom/vapor/protocol/bc/types"
"github.com/bytom/vapor/protocol/vm"
)
const (
- sizeOfCancelClauseArgs = 3
- sizeOfPartialTradeClauseArgs = 3
- sizeOfFullTradeClauseArgs = 2
+ sizeOfCancelClauseArgs = 3
+ sizeOfPartialTradeClauseArgsV1 = 3
+ sizeOfFullTradeClauseArgsV1 = 2
+ sizeOfPartialTradeClauseArgsV2 = 4
+ sizeOfFullTradeClauseArgsV2 = 3
)
-// smart contract clause select for differnet unlock method
+// smart contract clause select for different unlock method
const (
PartialTradeClauseSelector int64 = iota
FullTradeClauseSelector
// IsPartialTradeClauseSelector check if input select partial trade clause
func IsPartialTradeClauseSelector(input *types.TxInput) bool {
- return len(input.Arguments()) == sizeOfPartialTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(PartialTradeClauseSelector))
+ if len(input.Arguments()) == sizeOfPartialTradeClauseArgsV1 || len(input.Arguments()) == sizeOfPartialTradeClauseArgsV2 {
+ return hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(PartialTradeClauseSelector))
+ }
+ return false
}
// IsFullTradeClauseSelector check if input select full trade clause
func IsFullTradeClauseSelector(input *types.TxInput) bool {
- return len(input.Arguments()) == sizeOfFullTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(FullTradeClauseSelector))
+ if len(input.Arguments()) == sizeOfFullTradeClauseArgsV1 || len(input.Arguments()) == sizeOfFullTradeClauseArgsV2 {
+ return hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(FullTradeClauseSelector))
+ }
+ return false
+}
+
+// FeeRate return the rate of fee from input witness
+func FeeRate(input *types.TxInput) (int64, error) {
+ if IsFullTradeClauseSelector(input) {
+ if len(input.Arguments()) == sizeOfFullTradeClauseArgsV1 {
+ return 10, nil
+ }
+ return vm.AsInt64(input.Arguments()[0])
+ }
+ if IsPartialTradeClauseSelector(input) {
+ if len(input.Arguments()) == sizeOfPartialTradeClauseArgsV1 {
+ return 10, nil
+ }
+ return vm.AsInt64(input.Arguments()[1])
+ }
+ return 0, errors.New("invalid trade input")
}
}
}
-func addTakerOutput(txData *types.TxData, orders []*common.Order, priceDiffs []*bc.AssetAmount, isMakers []bool) {
+func addTakerOutput(txData *types.TxData, orders []*common.Order, priceDiffs []*bc.AssetAmount, makerFlags []*MakerFlag) {
for i := range orders {
- if isMakers[i] {
+ if makerFlags[i].IsMaker {
continue
}
for _, priceDiff := range priceDiffs {
return types.NewTx(*txData), partialOrders, nil
}
-func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets, isMakers []bool) ([]*common.Order, error) {
+func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets, makerFlags []*MakerFlag) ([]*common.Order, error) {
var partialOrders []*common.Order
for i, order := range orders {
contractArgs := order.ContractArgs
exchangeAmount := order.Utxo.Amount - shouldPayAmount
isPartialTrade := requestAmount > receivedAmount && CalcRequestAmount(exchangeAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) >= 1
- setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmount, isMakers[i])
+ setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmount, makerFlags[i].IsMaker, contractArgs.Version)
txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, allocatedAssets.Receives[i].Amount, contractArgs.SellerProgram))
if isPartialTrade {
return product.Cmp(one) <= 0
}
+// MakerFlag represent whether the order is isMaker and include the contract version of order
+type MakerFlag struct {
+ IsMaker bool
+ ContractVersion int
+}
+
// MakerFlags return a slice of array indicate whether orders[i] is maker
-func MakerFlags(orders []*common.Order) []bool {
- isMakers := make([]bool, len(orders))
+func MakerFlags(orders []*common.Order) []*MakerFlag {
+ makerFlags := make([]*MakerFlag, len(orders))
for i, order := range orders {
- isMakers[i] = isMaker(order, orders[calcOppositeIndex(len(orders), i)])
+ makerFlags[i].IsMaker = isMaker(order, orders[calcOppositeIndex(len(orders), i)])
+ makerFlags[i].ContractVersion = order.ContractArgs.Version
}
- return isMakers
+ return makerFlags
}
func isMaker(order, oppositeOrder *common.Order) bool {
return order.BlockHeight < oppositeOrder.BlockHeight
}
-func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64, isMaker bool) {
- feeRate := takerFeeRate
+func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64, isMaker bool, contractVersion int) {
+ feeRate := TakerFeeRate
if isMaker {
- feeRate = makerFeeRate
+ feeRate = MakerFeeRate
}
var arguments [][]byte
if isPartialTrade {
- arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector), vm.Int64Bytes(feeRate)}
+ arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts))}
+ if contractVersion == segwit.MagneticV2 {
+ arguments = append(arguments, vm.Int64Bytes(feeRate))
+ }
+ arguments = append(arguments, vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector))
} else {
- arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector), vm.Int64Bytes(feeRate)}
+ if contractVersion == segwit.MagneticV2 {
+ arguments = append(arguments, vm.Int64Bytes(feeRate))
+ }
+ arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
}
txInput.SetArguments(arguments)
}
import (
"math"
+ "github.com/bytom/vapor/consensus/segwit"
"github.com/bytom/vapor/errors"
"github.com/bytom/vapor/protocol/bc"
)
)
const (
- // rate of fee in units of 10000
- makerFeeRate int64 = 0
- takerFeeRate int64 = 3
+ // MakerFeeRate represent the fee rate of maker, which in units of 10000
+ MakerFeeRate int64 = 0
+ // TakerFeeRate represent the fee rate of taker
+ TakerFeeRate int64 = 5
)
// AllocatedAssets represent reallocated assets after calculating fees
// @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
// @param priceDiffs price differential of matching transaction, it will be refunded to the taker
// @return reallocated assets after calculating fees
- Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, isMakers []bool) *AllocatedAssets
+ Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, makerFlags []*MakerFlag) *AllocatedAssets
// Validate verify that the fee charged for a matching transaction is correct
- Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
+ Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, makerFlags []*MakerFlag) 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, priceDiffs []*bc.AssetAmount, isMakers []bool) *AllocatedAssets {
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, makerFlags []*MakerFlag) *AllocatedAssets {
receives := make([]*bc.AssetAmount, len(receiveAmounts))
fees := make([]*bc.AssetAmount, len(receiveAmounts))
for i, receiveAmount := range receiveAmounts {
- fee := d.calcFeeAmount(receiveAmount.Amount, isMakers[i])
+ makerFlag := makerFlags[i]
+ fee := calcFeeAmount(receiveAmount.Amount, makerFlag.IsMaker)
+ if makerFlag.ContractVersion == segwit.MagneticV1 {
+ fee = legendCalcMinFeeAmount(receiveAmount.Amount)
+ }
+
receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: receiveAmount.Amount - fee}
fees[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: fee}
- if !isMakers[i] {
+ if makerFlag.ContractVersion == segwit.MagneticV2 && !makerFlag.IsMaker {
for _, priceDiff := range priceDiffs {
if *priceDiff.AssetId == *receiveAmount.AssetId {
- fee = d.calcFeeAmount(priceDiff.Amount, false)
+ fee = calcFeeAmount(priceDiff.Amount, makerFlag.IsMaker)
priceDiff.Amount -= fee
fees[i].Amount += fee
}
}
// Validate verify that the fee charged for a matching transaction is correct
-func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
- for _, receiveAmount := range receiveAmounts {
- realFeeAmount := feeAmounts[*receiveAmount.AssetId]
- feeAmount := d.calcFeeAmount(receiveAmount.Amount, false)
- if realFeeAmount < feeAmount {
+func (d *DefaultFeeStrategy) Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, makerFlags []*MakerFlag) error {
+ for i, receiveAmount := range receiveAmounts {
+ receiveAssetID := receiveAmount.AssetId
+ feeAmount := feeAmounts[*receiveAssetID]
+
+ if makerFlags[i].ContractVersion == segwit.MagneticV1 {
+ return legendValidate(receiveAmount, feeAmount)
+ }
+
+ expectFee := calcFeeAmount(receiveAmount.Amount, makerFlags[i].IsMaker)
+ if !makerFlags[i].IsMaker {
+ for _, priceDiff := range priceDiffs {
+ if *priceDiff.AssetId == *receiveAssetID {
+ expectFee += calcFeeAmount(priceDiff.Amount, false)
+ }
+ }
+ }
+
+ if feeAmount != expectFee {
return ErrInvalidAmountOfFee
}
}
return nil
}
-func (d *DefaultFeeStrategy) calcFeeAmount(amount uint64, isMaker bool) uint64 {
- feeRate := takerFeeRate
+func calcFeeAmount(amount uint64, isMaker bool) uint64 {
+ feeRate := TakerFeeRate
if isMaker {
- feeRate = makerFeeRate
+ feeRate = MakerFeeRate
}
return uint64(math.Ceil(float64(amount) * float64(feeRate) / 1E4))
}
+
+func legendValidate(receiveAmount *bc.AssetAmount, feeAmount uint64) error {
+ maxFeeAmount := legendCalcMaxFeeAmount(receiveAmount.Amount)
+ minFeeAmount := legendCalcMinFeeAmount(receiveAmount.Amount)
+ if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
+ return ErrInvalidAmountOfFee
+ }
+ return nil
+}
+
+func legendCalcMinFeeAmount(amount uint64) uint64 {
+ return uint64(math.Ceil(float64(amount) / 1000))
+}
+
+func legendCalcMaxFeeAmount(amount uint64) uint64 {
+ return uint64(math.Ceil(float64(amount) * 0.05))
+}
errNotMatchedOrder = errors.New("order in matched tx is not matched")
errNotConfiguredRewardProgram = errors.New("reward program is not configured properly")
errRewardProgramIsWrong = errors.New("the reward program is not correct")
+ errInvalidFeeRate = errors.New("fee rate from input witness is invalid")
)
// Core represent the core logic of the match module, which include generate match transactions before packing the block,
return err
}
- receivedAmount, _ := match.CalcReceivedAmount(orders)
+ receivedAmount, priceDiffs := match.CalcReceivedAmount(orders)
feeAmounts := make(map[bc.AssetID]uint64)
for assetID, fee := range matchedTxFees {
feeAmounts[assetID] = fee.amount
}
+ makerFlags := make([]*match.MakerFlag, len(orders))
+ for i, order := range orders {
+ isMaker, err := isMakerByWitness(tx.Inputs[i], order.ContractArgs.Version)
+ if err != nil {
+ return err
+ }
+
+ makerFlags[i].IsMaker = isMaker
+ makerFlags[i].ContractVersion = order.ContractArgs.Version
+ }
+
feeStrategy := match.NewDefaultFeeStrategy()
- return feeStrategy.Validate(receivedAmount, feeAmounts)
+ return feeStrategy.Validate(receivedAmount, priceDiffs, feeAmounts, makerFlags)
+}
+
+func isMakerByWitness(input *types.TxInput, contractVersion int) (bool, error) {
+ if contractVersion == segwit.MagneticV1 {
+ return true, nil
+ }
+
+ feeRate, err := contract.FeeRate(input)
+ if err != nil {
+ return false, err
+ }
+
+ if feeRate != match.MakerFeeRate && feeRate != match.TakerFeeRate {
+ return false, errInvalidFeeRate
+ }
+
+ return feeRate == match.MakerFeeRate, nil
}
func (m *Core) validateMatchedTxSequence(txs []*Tx) error {
)
const (
- magneticV1 = iota + 1
- magneticV2
+ // MagneticV1 represent the V1 version of magnetic
+ MagneticV1 = iota + 1
+ // MagneticV2 represent the v2 version of magnetic
+ MagneticV2
)
// isMagneticScript is used to determine whether it is a Magnetic script with specific version or not
}
switch version {
- case magneticV1:
+ case MagneticV1:
if insts[0].Op != vm.OP_0 {
return false
}
- case magneticV2:
+ case MagneticV2:
if insts[0].Op != vm.OP_1 {
return false
}
// IsP2WMCScript is used to determine whether it is the v1 P2WMC script or not
func IsP2WMCScript(prog []byte) bool {
- return isMagneticScript(prog, magneticV1)
+ return isMagneticScript(prog, MagneticV1)
}
// IsP2WMCScriptV2 is used to determine whether it is the v2 P2WMC script or not
func IsP2WMCScriptV2(prog []byte) bool {
- return isMagneticScript(prog, magneticV2)
+ return isMagneticScript(prog, MagneticV2)
}
// DecodeP2WMCProgram parse standard P2WMC arguments to magneticContractArgs
SellerProgram: insts[4].Data,
SellerKey: insts[5].Data,
}
+
+ magneticContractArgs.Version = MagneticV2
+ if insts[0].Op == vm.OP_0 {
+ magneticContractArgs.Version = MagneticV1
+ }
+
requestedAsset := [32]byte{}
copy(requestedAsset[:], insts[1].Data)
magneticContractArgs.RequestedAsset = bc.NewAssetID(requestedAsset)
RatioDenominator int64
SellerProgram []byte
SellerKey []byte
+ Version int
}
// IsUnspendable checks if a contorl program is absolute failed