decred.org/dcrdex@v1.0.5/server/market/balancer_test.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package market
     5  
     6  import (
     7  	"testing"
     8  
     9  	"decred.org/dcrdex/dex"
    10  	"decred.org/dcrdex/dex/calc"
    11  )
    12  
    13  func TestBalancer(t *testing.T) {
    14  	const lotSize = 1e10
    15  
    16  	swapper := tNewMatchNegotiator()
    17  
    18  	ethTunnel := &TMarketTunnel{base: assetETH.ID}
    19  	ethBackend := &tAccountBackend{}
    20  	ethRedeemCost := tRedeemSize * assetETH.MaxFeeRate
    21  
    22  	tokenTunnel := &TMarketTunnel{quote: assetToken.ID}
    23  	tokenBackend := &tAccountBackend{}
    24  	// tokenRedeemCost := assetToken.RedeemSize * assetToken.MaxFeeRate
    25  
    26  	ethBalancer := &backedBalancer{
    27  		balancer:  ethBackend,
    28  		assetInfo: &assetETH.Asset,
    29  		markets: []PendingAccounter{
    30  			ethTunnel,
    31  		},
    32  		feeFamily: map[uint32]*dex.Asset{
    33  			assetToken.ID: &assetToken.Asset,
    34  		},
    35  	}
    36  
    37  	balancer := &DEXBalancer{
    38  		assets: map[uint32]*backedBalancer{
    39  			assetETH.ID: ethBalancer,
    40  			assetToken.ID: {
    41  				balancer:  tokenBackend,
    42  				assetInfo: &assetToken.Asset,
    43  				markets: []PendingAccounter{
    44  					tokenTunnel,
    45  				},
    46  				feeBalancer: ethBalancer,
    47  				feeFamily: map[uint32]*dex.Asset{
    48  					assetETH.ID: &assetETH.Asset,
    49  				},
    50  			},
    51  		},
    52  		matchNegotiator: swapper,
    53  	}
    54  
    55  	ethNineFive := calc.RequiredOrderFunds(lotSize*9, 0, 9, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate) + ethRedeemCost*5
    56  
    57  	type qlr struct {
    58  		qty, lots uint64
    59  		redeems   int
    60  	}
    61  
    62  	type assetParams struct {
    63  		bal     uint64
    64  		new     qlr
    65  		mkt     qlr
    66  		swapper qlr
    67  	}
    68  
    69  	type test struct {
    70  		name     string
    71  		pass     bool
    72  		eth      assetParams
    73  		token    assetParams
    74  		useToken bool
    75  		redeemID uint32
    76  	}
    77  
    78  	tests := []*test{
    79  		{
    80  			name: "no existing - 1 new lot - pass",
    81  			eth: assetParams{
    82  				new: qlr{lotSize, 1, 0},
    83  				bal: calc.RequiredOrderFunds(lotSize, 0, 1, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate),
    84  			},
    85  			pass: true,
    86  		}, {
    87  			name: "no existing - 1 new lot - fail",
    88  			eth: assetParams{
    89  				new: qlr{lotSize, 1, 0},
    90  				bal: calc.RequiredOrderFunds(lotSize, 0, 1, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate) - 1,
    91  			},
    92  		}, {
    93  			name: "no existing - 1 new redeem - pass",
    94  			eth: assetParams{
    95  				new: qlr{0, 0, 1},
    96  				bal: ethRedeemCost,
    97  			},
    98  			pass: true,
    99  		}, {
   100  			name: "no existing - 1 new redeem - fail",
   101  			eth: assetParams{
   102  				new: qlr{0, 0, 1},
   103  				bal: ethRedeemCost - 1,
   104  			},
   105  		}, {
   106  			name: "1 each swapper - 1 new lot - pass",
   107  			eth: assetParams{
   108  				new:     qlr{lotSize, 1, 0},
   109  				swapper: qlr{lotSize, 1, 1},
   110  				bal:     calc.RequiredOrderFunds(lotSize*2, 0, 2, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate) + ethRedeemCost,
   111  			},
   112  			pass: true,
   113  		}, {
   114  			name: "1 each market - 1 new lot - pass",
   115  			eth: assetParams{
   116  				new: qlr{lotSize, 1, 0},
   117  				mkt: qlr{lotSize, 1, 1},
   118  				bal: calc.RequiredOrderFunds(lotSize*2, 0, 2, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate) + ethRedeemCost,
   119  			},
   120  			pass: true,
   121  		},
   122  		{
   123  			name: "1 each market - 1 new lot - fail",
   124  			eth: assetParams{
   125  				new: qlr{lotSize, 1, 0},
   126  				mkt: qlr{lotSize, 1, 1},
   127  				bal: calc.RequiredOrderFunds(lotSize*2, 0, 2, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate) + ethRedeemCost - 1,
   128  			},
   129  		},
   130  		{
   131  			name: "mix it up - pass",
   132  			eth: assetParams{
   133  				new:     qlr{lotSize * 2, 2, 0},
   134  				mkt:     qlr{lotSize * 3, 3, 2},
   135  				swapper: qlr{lotSize * 4, 4, 3},
   136  				bal:     ethNineFive,
   137  			},
   138  			pass: true,
   139  		},
   140  		{
   141  			name: "mix it up - fail",
   142  			eth: assetParams{
   143  				new:     qlr{lotSize * 2, 2, 0},
   144  				mkt:     qlr{lotSize * 3, 3, 2},
   145  				swapper: qlr{lotSize * 4, 4, 3},
   146  				bal:     ethNineFive - 1,
   147  			},
   148  		},
   149  		{
   150  			name: "mix it up - with tokens - pass",
   151  			eth: assetParams{
   152  				new:     qlr{lotSize * 2, 2, 0},
   153  				mkt:     qlr{lotSize * 3, 3, 2},
   154  				swapper: qlr{lotSize * 4, 4, 3},
   155  				bal:     ethNineFive + assetToken.MaxFeeRate*(tInitTxSize+tRedeemSize),
   156  			},
   157  			token: assetParams{
   158  				mkt: qlr{lotSize * 5000, 1, 1}, // qty doesn't shouldn't matter.
   159  			},
   160  			pass: true,
   161  		},
   162  		{
   163  			name: "mix it up - with tokens - fail",
   164  			eth: assetParams{
   165  				new:     qlr{lotSize * 2, 2, 0},
   166  				mkt:     qlr{lotSize * 3, 3, 2},
   167  				swapper: qlr{lotSize * 4, 4, 3},
   168  				bal:     ethNineFive + assetToken.MaxFeeRate*(tInitTxSize+tRedeemSize) - 1,
   169  			},
   170  			token: assetParams{
   171  				mkt: qlr{lotSize * 5000, 1, 1},
   172  			},
   173  		}, {
   174  			name:     "1 new lot token - pass",
   175  			useToken: true,
   176  			token: assetParams{
   177  				new: qlr{lotSize, 1, 0},
   178  				bal: lotSize,
   179  			},
   180  			eth: assetParams{
   181  				bal: tInitTxSize * assetToken.MaxFeeRate,
   182  			},
   183  			pass: true,
   184  		}, {
   185  			name:     "1 new lot token - fail insufficient fees",
   186  			useToken: true,
   187  			token: assetParams{
   188  				new: qlr{lotSize, 1, 0},
   189  				bal: lotSize,
   190  			},
   191  			eth: assetParams{
   192  				bal: tInitTxSize*assetToken.MaxFeeRate - 1,
   193  			},
   194  		}, {
   195  			name:     "1 new lot token - fail insufficient balance",
   196  			useToken: true,
   197  			token: assetParams{
   198  				new: qlr{lotSize, 1, 0},
   199  				bal: lotSize - 1,
   200  			},
   201  			eth: assetParams{
   202  				bal: tInitTxSize * assetToken.MaxFeeRate,
   203  			},
   204  		}, {
   205  			name:     "1 new lot token - family redeem - pass",
   206  			useToken: true,
   207  			token: assetParams{
   208  				new: qlr{lotSize, 1, 0},
   209  				bal: lotSize,
   210  			},
   211  			eth: assetParams{
   212  				bal: tInitTxSize*assetToken.MaxFeeRate + tRedeemSize*assetETH.MaxFeeRate,
   213  			},
   214  			redeemID: assetETH.ID,
   215  			pass:     true,
   216  		}, {
   217  			name:     "1 new lot token - family redeem - fail insufficient fees",
   218  			useToken: true,
   219  			token: assetParams{
   220  				new: qlr{2 * lotSize, 1, 0},
   221  				bal: lotSize,
   222  			},
   223  			eth: assetParams{
   224  				bal: tInitTxSize*assetToken.MaxFeeRate + tRedeemSize*assetETH.MaxFeeRate - 1,
   225  			},
   226  			redeemID: assetETH.ID,
   227  		}, {
   228  			name:     "2 new lot token - family redeem + 2 pending token redeems - pass",
   229  			useToken: true,
   230  			token: assetParams{
   231  				new: qlr{2 * lotSize, 2, 0},
   232  				mkt: qlr{0, 0, 2},
   233  				bal: 2 * lotSize,
   234  			},
   235  			eth: assetParams{
   236  				bal: 2*(tInitTxSize*assetToken.MaxFeeRate+tRedeemSize*assetETH.MaxFeeRate) +
   237  					2*tRedeemSize*assetToken.MaxFeeRate,
   238  			},
   239  			redeemID: assetETH.ID,
   240  			pass:     true,
   241  		}, {
   242  			name:     "2 new lot token - family redeem + 2 pending token redeems - fail insufficient fees",
   243  			useToken: true,
   244  			token: assetParams{
   245  				new: qlr{2 * lotSize, 2, 0},
   246  				mkt: qlr{0, 0, 2},
   247  				bal: 2 * lotSize,
   248  			},
   249  			eth: assetParams{
   250  				bal: 2*(tInitTxSize*assetToken.MaxFeeRate+tRedeemSize*assetETH.MaxFeeRate) +
   251  					2*tRedeemSize*assetToken.MaxFeeRate - 1,
   252  			},
   253  			redeemID: assetETH.ID,
   254  		},
   255  		{
   256  			name:     "2 new lot token - family redeem + 2 pending token redeems - fail insufficient balance",
   257  			useToken: true,
   258  			token: assetParams{
   259  				new: qlr{2 * lotSize, 2, 0},
   260  				mkt: qlr{0, 0, 2},
   261  				bal: 2*lotSize - 1,
   262  			},
   263  			eth: assetParams{
   264  				bal: 2*(tInitTxSize*assetToken.MaxFeeRate+tRedeemSize*assetETH.MaxFeeRate) +
   265  					2*tRedeemSize*assetToken.MaxFeeRate,
   266  			},
   267  			redeemID: assetETH.ID,
   268  		},
   269  	}
   270  
   271  	for _, tt := range tests {
   272  		ethTunnel.acctLots = tt.eth.mkt.lots
   273  		ethTunnel.acctQty = tt.eth.mkt.qty
   274  		ethTunnel.acctRedeems = tt.eth.mkt.redeems
   275  		swapper.swaps[assetETH.ID] = tt.eth.swapper.lots
   276  		swapper.qty[assetETH.ID] = tt.eth.swapper.qty
   277  		swapper.redeems[assetETH.ID] = tt.eth.swapper.redeems
   278  		ethBackend.bal = tt.eth.bal
   279  
   280  		tokenTunnel.acctLots = tt.token.mkt.lots
   281  		tokenTunnel.acctQty = tt.token.mkt.qty
   282  		tokenTunnel.acctRedeems = tt.token.mkt.redeems
   283  		swapper.swaps[assetToken.ID] = tt.token.swapper.lots
   284  		swapper.qty[assetToken.ID] = tt.token.swapper.qty
   285  		swapper.redeems[assetToken.ID] = tt.token.swapper.redeems
   286  		tokenBackend.bal = tt.token.bal
   287  
   288  		assetID := assetETH.ID
   289  		newQLR := tt.eth.new
   290  		if tt.useToken {
   291  			assetID = assetToken.ID
   292  			newQLR = tt.token.new
   293  		}
   294  
   295  		if balancer.CheckBalance("a", assetID, tt.redeemID, newQLR.qty, newQLR.lots, newQLR.redeems) != tt.pass {
   296  			t.Fatalf("%s: expected %t, got %t", tt.name, tt.pass, !tt.pass)
   297  		}
   298  	}
   299  }