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 }