import (
"testing"
- "github.com/vapor/protocol/vm"
- "github.com/vapor/testutil"
-
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/database"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/vm/vmutil"
+ "github.com/bytom/vapor/application/mov/common"
+ "github.com/bytom/vapor/application/mov/mock"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/bc/types"
+ "github.com/bytom/vapor/protocol/validation"
+ "github.com/bytom/vapor/testutil"
)
-var (
- btc = bc.NewAssetID([32]byte{1})
- eth = bc.NewAssetID([32]byte{2})
+func TestGenerateMatchedTxs(t *testing.T) {
+ btc2eth := &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+ eth2btc := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.BTC}
+ eth2eos := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.EOS}
+ eos2btc := &common.TradePair{FromAssetID: &mock.EOS, ToAssetID: &mock.BTC}
- orders = []*common.Order{
- // btc -> eth
- {
- FromAssetID: &btc,
- ToAssetID: ð,
- Rate: 50,
- Utxo: &common.MovUtxo{
- SourceID: hashPtr(testutil.MustDecodeHash("37b8edf656e45a7addf47f5626e114a8c394d918a36f61b5a2905675a09b40ae")),
- SourcePos: 0,
- Amount: 10,
- ControlProgram: mustCreateP2WMCProgram(eth, testutil.MustDecodeHexString("51"), 50, 1),
- },
- },
+ cases := []struct {
+ desc string
+ tradePairs []*common.TradePair
+ initStoreOrders []*common.Order
+ wantMatchedTxs []*types.Tx
+ }{
{
- FromAssetID: &btc,
- ToAssetID: ð,
- Rate: 53,
- Utxo: &common.MovUtxo{
- SourceID: hashPtr(testutil.MustDecodeHash("3ec2bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
- SourcePos: 0,
- Amount: 20,
- ControlProgram: mustCreateP2WMCProgram(eth, testutil.MustDecodeHexString("52"), 53, 1),
+ desc: "full matched",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[0],
},
- },
-
- // eth -> btc
- {
- FromAssetID: ð,
- ToAssetID: &btc,
- Rate: 1 / 51.0,
- Utxo: &common.MovUtxo{
- SourceID: hashPtr(testutil.MustDecodeHash("fba43ff5155209cb1769e2ec0e1d4a33accf899c740865edfc6d1de39b873b29")),
- SourcePos: 0,
- Amount: 510,
- ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("53"), 1, 51.0),
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[1],
},
},
{
- FromAssetID: ð,
- ToAssetID: &btc,
- Rate: 1 / 52.0,
- Utxo: &common.MovUtxo{
- SourceID: hashPtr(testutil.MustDecodeHash("05f24bb847db823075d81786aa270748e02602199cd009c0284f928503846a5a")),
- SourcePos: 0,
- Amount: 416,
- ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("54"), 1, 52.0),
+ desc: "partial matched",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[1],
},
- },
- {
- FromAssetID: ð,
- ToAssetID: &btc,
- Rate: 1 / 54.0,
- Utxo: &common.MovUtxo{
- SourceID: hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d201")),
- SourcePos: 0,
- Amount: 810,
- ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("55"), 1, 54.0),
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[0],
},
},
- }
-)
-
-func TestGenerateMatchedTxs(t *testing.T) {
- btc2eth := &common.TradePair{FromAssetID: &btc, ToAssetID: ð}
- eth2btc := &common.TradePair{FromAssetID: ð, ToAssetID: &btc}
-
- cases := []struct {
- desc string
- tradePair *common.TradePair
- storeOrderMap map[string][]*common.Order
- wantMatchedTxs []*types.TxData
- }{
{
- desc: "full matched",
- tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: ð},
- storeOrderMap: map[string][]*common.Order{
- btc2eth.Key(): {orders[0], orders[1]},
- eth2btc.Key(): {orders[2], orders[3]},
+ desc: "partial matched and continue to match",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[2],
},
- wantMatchedTxs: []*types.TxData{
- {
- Inputs: []*types.TxInput{
- types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *orders[0].Utxo.SourceID, *orders[0].FromAssetID, orders[0].Utxo.Amount, orders[0].Utxo.SourcePos, orders[0].Utxo.ControlProgram),
- types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *orders[2].Utxo.SourceID, *orders[2].FromAssetID, orders[2].Utxo.Amount, orders[2].Utxo.SourcePos, orders[2].Utxo.ControlProgram),
- },
- Outputs: []*types.TxOutput{
- types.NewIntraChainOutput(*orders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
- types.NewIntraChainOutput(*orders[2].ToAssetID, 10, testutil.MustDecodeHexString("53")),
- types.NewIntraChainOutput(*orders[0].ToAssetID, 10, []byte{0x51}),
- },
- },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[2],
+ mock.MatchedTxs[3],
},
},
{
- desc: "partial matched",
- tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: ð},
- storeOrderMap: map[string][]*common.Order{
- btc2eth.Key(): {orders[0], orders[1]},
- eth2btc.Key(): {orders[3]},
- },
- wantMatchedTxs: []*types.TxData{
- {
- Inputs: []*types.TxInput{
- types.NewSpendInput([][]byte{vm.Int64Bytes(416), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *orders[0].Utxo.SourceID, *orders[0].FromAssetID, orders[0].Utxo.Amount, orders[0].Utxo.SourcePos, orders[0].Utxo.ControlProgram),
- types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *orders[3].Utxo.SourceID, *orders[3].FromAssetID, orders[3].Utxo.Amount, orders[3].Utxo.SourcePos, orders[3].Utxo.ControlProgram),
- },
- Outputs: []*types.TxOutput{
- types.NewIntraChainOutput(*orders[0].ToAssetID, 416, testutil.MustDecodeHexString("51")),
- // re-order
- types.NewIntraChainOutput(*orders[0].FromAssetID, 2, orders[0].Utxo.ControlProgram),
- types.NewIntraChainOutput(*orders[3].ToAssetID, 8, testutil.MustDecodeHexString("54")),
- },
- },
+ desc: "unable to match",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[0],
},
+ wantMatchedTxs: []*types.Tx{},
},
{
- desc: "partial matched and continue to match",
- tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: ð},
- storeOrderMap: map[string][]*common.Order{
- btc2eth.Key(): {orders[0], orders[1]},
- eth2btc.Key(): {orders[4]},
+ desc: "cycle match",
+ tradePairs: []*common.TradePair{btc2eth, eth2eos, eos2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Eth2EosOrders[0], mock.Eos2BtcOrders[0],
},
- wantMatchedTxs: []*types.TxData{
- {
- Inputs: []*types.TxInput{
- types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *orders[0].Utxo.SourceID, *orders[0].FromAssetID, orders[0].Utxo.Amount, orders[0].Utxo.SourcePos, orders[0].Utxo.ControlProgram),
- types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *orders[4].Utxo.SourceID, *orders[4].FromAssetID, orders[4].Utxo.Amount, orders[4].Utxo.SourcePos, orders[4].Utxo.ControlProgram),
- },
- Outputs: []*types.TxOutput{
- types.NewIntraChainOutput(*orders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
- types.NewIntraChainOutput(*orders[4].ToAssetID, 10, testutil.MustDecodeHexString("55")),
- // re-order
- types.NewIntraChainOutput(*orders[4].FromAssetID, 270, orders[4].Utxo.ControlProgram),
- // fee
- types.NewIntraChainOutput(*orders[4].FromAssetID, 27, []byte{0x51}),
- // refund
- types.NewIntraChainOutput(*orders[4].FromAssetID, 6, testutil.MustDecodeHexString("51")),
- types.NewIntraChainOutput(*orders[4].FromAssetID, 7, testutil.MustDecodeHexString("55")),
- },
- },
- {
- Inputs: []*types.TxInput{
- types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(0)}, *orders[1].Utxo.SourceID, *orders[1].FromAssetID, orders[1].Utxo.Amount, orders[1].Utxo.SourcePos, orders[1].Utxo.ControlProgram),
- types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("f47177c12d25f5316eb377ea006e77bf07e4f9646860e4641e313e004f9aa989"), *orders[4].FromAssetID, 270, 2, orders[4].Utxo.ControlProgram),
- },
- Outputs: []*types.TxOutput{
- types.NewIntraChainOutput(*orders[1].ToAssetID, 270, testutil.MustDecodeHexString("52")),
- // re-order
- types.NewIntraChainOutput(*orders[1].FromAssetID, 15, orders[1].Utxo.ControlProgram),
- types.NewIntraChainOutput(*orders[4].ToAssetID, 5, testutil.MustDecodeHexString("55")),
- },
- },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[6],
},
},
}
for i, c := range cases {
- movStore := &database.MockMovStore{OrderMap: c.storeOrderMap}
- matchEngine := NewEngine(NewOrderTable(movStore, nil, nil), 0.05, []byte{0x51})
+ movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
+ matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), 0.05, mock.NodeProgram)
var gotMatchedTxs []*types.Tx
- for matchEngine.HasMatchedTx(c.tradePair, c.tradePair.Reverse()) {
- matchedTx, err := matchEngine.NextMatchedTx(c.tradePair, c.tradePair.Reverse())
+ for matchEngine.HasMatchedTx(c.tradePairs...) {
+ matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
if err != nil {
t.Fatal(err)
}
}
for i, gotMatchedTx := range gotMatchedTxs {
+ if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
+ t.Fatal(err)
+ }
+
c.wantMatchedTxs[i].Version = 1
byteData, err := c.wantMatchedTxs[i].MarshalText()
if err != nil {
}
c.wantMatchedTxs[i].SerializedSize = uint64(len(byteData))
- wantMatchedTx := types.NewTx(*c.wantMatchedTxs[i])
+ wantMatchedTx := types.NewTx(c.wantMatchedTxs[i].TxData)
if gotMatchedTx.ID != wantMatchedTx.ID {
t.Errorf("#%d(%s) the tx hash of got matched tx: %s is not equals want matched tx: %s", i, c.desc, gotMatchedTx.ID.String(), wantMatchedTx.ID.String())
}
}
}
-func mustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioMolecule, ratioDenominator int64) []byte {
- contractArgs := vmutil.MagneticContractArgs{
- RequestedAsset: requestAsset,
- RatioNumerator: ratioMolecule,
- RatioDenominator: ratioDenominator,
- SellerProgram: sellerProgram,
- SellerKey: testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
+func TestCalcMatchedTxFee(t *testing.T) {
+ cases := []struct {
+ desc string
+ tx *types.TxData
+ maxFeeRate float64
+ wantMatchedTxFee map[bc.AssetID]*MatchedTxFee
+ }{
+ {
+ desc: "fee less than max fee",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 10, MaxFeeAmount: 26}},
+ tx: &mock.MatchedTxs[1].TxData,
+ },
+ {
+ desc: "fee refund in tx",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 27, MaxFeeAmount: 27}},
+ tx: &mock.MatchedTxs[2].TxData,
+ },
+ {
+ desc: "fee is zero",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{},
+ tx: &mock.MatchedTxs[0].TxData,
+ },
}
- program, err := vmutil.P2WMCProgram(contractArgs)
- if err != nil {
- panic(err)
+
+ for i, c := range cases {
+ gotMatchedTxFee, err := CalcMatchedTxFee(c.tx, c.maxFeeRate)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
+ t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
+ }
}
- return program
}
-func hashPtr(hash bc.Hash) *bc.Hash {
- return &hash
+func TestValidateTradePairs(t *testing.T) {
+ cases := []struct {
+ desc string
+ tradePairs []*common.TradePair
+ wantError bool
+ }{
+ {
+ desc: "valid case of two trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ },
+ },
+ wantError: false,
+ },
+ {
+ desc: "invalid case of two trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ },
+ },
+ wantError: true,
+ },
+ {
+ desc: "valid case of three trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ },
+ },
+ wantError: false,
+ },
+ {
+ desc: "invalid case of three trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ },
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.BTC,
+ },
+ },
+ wantError: true,
+ },
+ {
+ desc: "valid case 2 of three trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ },
+ },
+ wantError: false,
+ },
+ }
+
+ for i, c := range cases {
+ err := validateTradePairs(c.tradePairs)
+ if c.wantError && err == nil {
+ t.Errorf("#%d(%s): want error, got no error", i, c.desc)
+ }
+
+ if !c.wantError && err != nil {
+ t.Errorf("#%d(%s): want no error, got error (%v)", i, c.desc, err)
+ }
+ }
+}
+
+func TestIsMatched(t *testing.T) {
+ cases := []struct {
+ desc string
+ orders []*common.Order
+ wantMatched bool
+ }{
+ {
+ desc: "ratio is equals",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 6250,
+ RatioDenominator: 3,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 3,
+ RatioDenominator: 6250,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "ratio is equals, and numerator and denominator are multiples of the opposite",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 6250,
+ RatioDenominator: 3,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 9,
+ RatioDenominator: 18750,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 62500000000000000,
+ RatioDenominator: 30000000000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 3,
+ RatioDenominator: 6250,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "not matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 62500000000000001,
+ RatioDenominator: 30000000000000,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 3,
+ RatioDenominator: 6250,
+ },
+ },
+ wantMatched: false,
+ },
+ {
+ desc: "ring matched",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 2000000003,
+ RatioDenominator: 100000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ RatioNumerator: 400000000001,
+ RatioDenominator: 2000000003,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 100000001,
+ RatioDenominator: 400000000001,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "ring matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 2000000003,
+ RatioDenominator: 100000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ RatioNumerator: 400000000001,
+ RatioDenominator: 2000000003,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 100000000,
+ RatioDenominator: 400000000001,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "ring fail matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 2000000003,
+ RatioDenominator: 100000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ RatioNumerator: 400000000001,
+ RatioDenominator: 2000000003,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 100000002,
+ RatioDenominator: 400000000001,
+ },
+ },
+ wantMatched: false,
+ },
+ }
+
+ for i, c := range cases {
+ gotMatched := IsMatched(c.orders)
+ if gotMatched != c.wantMatched {
+ t.Errorf("#%d(%s) got matched:%v, want matched:%v", i, c.desc, gotMatched, c.wantMatched)
+ }
+ }
}