code.vegaprotocol.io/vega@v0.79.0/core/execution/future/liquidity_provision_test.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package future_test 17 18 import ( 19 "context" 20 "testing" 21 "time" 22 23 "code.vegaprotocol.io/vega/core/events" 24 "code.vegaprotocol.io/vega/core/types" 25 vegacontext "code.vegaprotocol.io/vega/libs/context" 26 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 27 "code.vegaprotocol.io/vega/libs/num" 28 proto "code.vegaprotocol.io/vega/protos/vega" 29 30 "github.com/golang/mock/gomock" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 func TestSubmit(t *testing.T) { 36 pMonitorSettings := &types.PriceMonitoringSettings{ 37 Parameters: &types.PriceMonitoringParameters{ 38 Triggers: []*types.PriceMonitoringTrigger{}, 39 }, 40 } 41 now := time.Unix(10, 0) 42 block := time.Second 43 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 44 45 t.Run("check that we reject LP submission If fee is incorrect", func(t *testing.T) { 46 tm := getTestMarket(t, now, nil, nil) 47 48 // Create a new party account with very little funding 49 addAccountWithAmount(tm, "party-A", 100000000) 50 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 51 52 // Start the opening auction 53 tm.mas.StartOpeningAuction(tm.now, &types.AuctionDuration{Duration: 10}) 54 tm.mas.AuctionStarted(ctx, tm.now) 55 tm.market.EnterAuction(ctx) 56 57 // Submitting a zero or smaller fee should cause a reject 58 lps := &types.LiquidityProvisionSubmission{ 59 Fee: num.DecimalFromFloat(-0.50), 60 MarketID: tm.market.GetID(), 61 CommitmentAmount: num.NewUint(1000), 62 } 63 64 err := tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 65 require.Error(t, err) 66 assert.Equal(t, 0, tm.market.GetLPSCount()) 67 68 // Submitting a fee greater than 1.0 should cause a reject 69 lps = &types.LiquidityProvisionSubmission{ 70 Fee: num.DecimalFromFloat(1.01), 71 MarketID: tm.market.GetID(), 72 CommitmentAmount: num.NewUint(1000), 73 } 74 75 err = tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 76 require.Error(t, err) 77 assert.Equal(t, 0, tm.market.GetLPSCount()) 78 }) 79 80 t.Run("test liquidity provision fee validation", func(t *testing.T) { 81 pMonitorSettings := &types.PriceMonitoringSettings{ 82 Parameters: &types.PriceMonitoringParameters{ 83 Triggers: []*types.PriceMonitoringTrigger{}, 84 }, 85 } 86 mktCfg := getMarket(pMonitorSettings, &types.AuctionDuration{ 87 Duration: 10000, 88 }) 89 mktCfg.Fees.Factors = &types.FeeFactors{ 90 LiquidityFee: num.DecimalFromFloat(0.001), 91 InfrastructureFee: num.DecimalFromFloat(0.0005), 92 MakerFee: num.DecimalFromFloat(0.00025), 93 } 94 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 95 LogNormalRiskModel: &types.LogNormalRiskModel{ 96 RiskAversionParameter: num.DecimalFromFloat(0.001), 97 Tau: num.DecimalFromFloat(0.00011407711613050422), 98 Params: &types.LogNormalModelParams{ 99 Mu: num.DecimalZero(), 100 R: num.DecimalFromFloat(0.016), 101 Sigma: num.DecimalFromFloat(20), 102 }, 103 }, 104 } 105 106 lpparty := "lp-party-1" 107 108 tm := newTestMarket(t, now).Run(ctx, mktCfg) 109 tm.StartOpeningAuction(). 110 // the liquidity provider 111 WithAccountAndAmount(lpparty, 50000000000000) 112 113 tm.market.OnTick(ctx, tm.now) 114 115 // Add a LPSubmission 116 // this is a log of stake, enough to cover all 117 // the required stake for the market 118 lpSubmission := &types.LiquidityProvisionSubmission{ 119 MarketID: tm.market.GetID(), 120 CommitmentAmount: num.NewUint(70000), 121 Fee: num.DecimalFromFloat(-0.1), 122 Reference: "ref-lp-submission-1", 123 } 124 125 // submit our lp 126 require.EqualError(t, 127 tm.market.SubmitLiquidityProvision( 128 ctx, lpSubmission, lpparty, vgcrypto.RandomHash()), 129 "invalid liquidity provision fee", 130 ) 131 132 lpSubmission.Fee = num.DecimalFromFloat(10) 133 134 // submit our lp 135 require.EqualError(t, 136 tm.market.SubmitLiquidityProvision( 137 ctx, lpSubmission, lpparty, vgcrypto.RandomHash()), 138 "invalid liquidity provision fee", 139 ) 140 141 lpSubmission.Fee = num.DecimalZero() 142 143 // submit our lp 144 require.NoError(t, 145 tm.market.SubmitLiquidityProvision( 146 ctx, lpSubmission, lpparty, vgcrypto.RandomHash()), 147 ) 148 }) 149 150 t.Run("check we can submit LP during price auction", func(t *testing.T) { 151 hdec := num.DecimalFromFloat(60) 152 pMonitorSettings := &types.PriceMonitoringSettings{ 153 Parameters: &types.PriceMonitoringParameters{ 154 Triggers: []*types.PriceMonitoringTrigger{ 155 { 156 Horizon: 60, 157 HorizonDec: hdec, 158 Probability: num.DecimalFromFloat(0.15), 159 AuctionExtension: 60, 160 }, 161 }, 162 }, 163 } 164 165 tm := getTestMarket(t, now, pMonitorSettings, nil) 166 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, 10*time.Second) 167 168 // Create a new party account with very little funding 169 addAccountWithAmount(tm, "party-A", 70000000) 170 addAccountWithAmount(tm, "party-B", 10000000) 171 addAccountWithAmount(tm, "party-C", 10000000) 172 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 173 174 tm.mas.StartOpeningAuction(tm.now, &types.AuctionDuration{Duration: 10}) 175 tm.mas.AuctionStarted(ctx, tm.now) 176 tm.market.EnterAuction(ctx) 177 178 lpParty := "lp-party-1" 179 addAccountWithAmount(tm, lpParty, 5000000) 180 181 // ensure LP is set 182 lps := &types.LiquidityProvisionSubmission{ 183 Fee: num.DecimalFromFloat(0.01), 184 MarketID: tm.market.GetID(), 185 CommitmentAmount: num.NewUint(5000000), 186 } 187 require.NoError(t, tm.market.SubmitLiquidityProvision( 188 context.Background(), lps, lpParty, vgcrypto.RandomHash(), 189 )) 190 191 // Create some normal orders to set the reference prices 192 o1 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-B", 10, 1000) 193 o1conf, err := tm.market.SubmitOrder(ctx, o1) 194 require.NotNil(t, o1conf) 195 require.NoError(t, err) 196 197 o2 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-C", 2, 1000) 198 o2conf, err := tm.market.SubmitOrder(ctx, o2) 199 require.NotNil(t, o2conf) 200 require.NoError(t, err) 201 202 o3 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order03", types.SideSell, "party-C", 1, 2000) 203 o3conf, err := tm.market.SubmitOrder(ctx, o3) 204 require.NotNil(t, o3conf) 205 require.NoError(t, err) 206 207 o4 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order04", types.SideSell, "party-C", 10, 3000) 208 o4conf, err := tm.market.SubmitOrder(ctx, o4) 209 require.NotNil(t, o4conf) 210 require.NoError(t, err) 211 212 assert.Equal(t, types.AuctionTriggerOpening, tm.market.GetMarketData().Trigger) 213 // Leave the auction so we can uncross the book 214 tm.now = tm.now.Add(block * 11) 215 tm.market.OnTick(ctx, tm.now) 216 // ensure we left auction 217 assert.Equal(t, types.AuctionTriggerUnspecified, tm.market.GetMarketData().Trigger) 218 219 // Move the price enough that we go into a price auction 220 o5 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order05", types.SideBuy, "party-B", 3, 3000) 221 o5conf, err := tm.market.SubmitOrder(ctx, o5) 222 require.NotNil(t, o5conf) 223 require.NoError(t, err) 224 225 // Check we are in price auction 226 assert.Equal(t, types.AuctionTriggerPrice, tm.market.GetMarketData().Trigger) 227 228 // Submitting a correct entry 229 lps2 := &types.LiquidityProvisionSubmission{ 230 Fee: num.DecimalFromFloat(0.01), 231 MarketID: tm.market.GetID(), 232 CommitmentAmount: num.NewUint(1000), 233 } 234 235 err = tm.market.SubmitLiquidityProvision(ctx, lps2, "party-A", vgcrypto.RandomHash()) 236 237 tm.market.OnEpochEvent(ctx, types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_START}) 238 require.NoError(t, err) 239 require.Equal(t, types.LiquidityProvisionStatusActive.String(), tm.market.GetLPSState("party-A").String()) 240 // Only 3 pegged orders as one fails due to price monitoring 241 assert.Equal(t, 0, tm.market.GetPeggedOrderCount()) 242 }) 243 244 t.Run("check that rejected market stops liquidity provision", func(t *testing.T) { 245 mktCfg := getMarket(pMonitorSettings, &types.AuctionDuration{ 246 Duration: 10000, 247 }) 248 mktCfg.Fees.Factors = &types.FeeFactors{ 249 InfrastructureFee: num.DecimalFromFloat(0.0005), 250 MakerFee: num.DecimalFromFloat(0.00025), 251 } 252 253 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 254 LogNormalRiskModel: &types.LogNormalRiskModel{ 255 RiskAversionParameter: num.DecimalFromFloat(0.001), 256 Tau: num.DecimalFromFloat(0.00011407711613050422), 257 Params: &types.LogNormalModelParams{ 258 Mu: num.DecimalZero(), 259 R: num.DecimalFromFloat(0.016), 260 Sigma: num.DecimalFromFloat(2), 261 }, 262 }, 263 } 264 265 lpparty := "lp-party-1" 266 267 tm := newTestMarket(t, now).Run(ctx, mktCfg) 268 tm.WithAccountAndAmount(lpparty, 100000000000000) 269 tm.now = now 270 tm.market.OnTick(ctx, now) 271 272 // Add a LPSubmission 273 // this is a log of stake, enough to cover all 274 // the required stake for the market 275 lpSubmission := &types.LiquidityProvisionSubmission{ 276 MarketID: tm.market.GetID(), 277 CommitmentAmount: num.NewUint(10000000), 278 Fee: num.DecimalFromFloat(0.5), 279 Reference: "ref-lp-submission-1", 280 } 281 282 // submit our lp 283 tm.events = nil 284 require.NoError(t, 285 tm.market.SubmitLiquidityProvision( 286 ctx, lpSubmission, lpparty, vgcrypto.RandomHash()), 287 ) 288 289 t.Run("lp submission is pending", func(t *testing.T) { 290 // First collect all the orders events 291 var found *proto.LiquidityProvision 292 for _, e := range tm.events { 293 switch evt := e.(type) { 294 case *events.LiquidityProvision: 295 found = evt.LiquidityProvision() 296 } 297 } 298 require.NotNil(t, found) 299 // no update to the liquidity fee 300 assert.Equal(t, found.Status.String(), types.LiquidityProvisionStatusActive.String()) 301 }) 302 303 tm.events = nil 304 require.NoError( 305 t, 306 tm.market.Reject(context.Background()), 307 ) 308 309 t.Run("lp submission is stopped", func(t *testing.T) { 310 // First collect all the orders events 311 var found *proto.LiquidityProvision 312 for _, e := range tm.events { 313 switch evt := e.(type) { 314 case *events.LiquidityProvision: 315 found = evt.LiquidityProvision() 316 } 317 } 318 require.NotNil(t, found) 319 // no update to the liquidity fee 320 assert.Equal(t, found.Status.String(), types.LiquidityProvisionStatusStopped.String()) 321 }) 322 }) 323 } 324 325 func TestAmend(t *testing.T) { 326 now := time.Unix(10, 0) 327 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 328 329 t.Run("check that fee is selected properly after changes", func(t *testing.T) { 330 auctionEnd := now.Add(10001 * time.Second) 331 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 332 Duration: 10000, 333 }) 334 mktCfg.Fees.Factors = &types.FeeFactors{ 335 InfrastructureFee: num.DecimalFromFloat(0.0005), 336 MakerFee: num.DecimalFromFloat(0.00025), 337 } 338 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 339 LogNormalRiskModel: &types.LogNormalRiskModel{ 340 RiskAversionParameter: num.DecimalFromFloat(0.001), 341 Tau: num.DecimalFromFloat(0.00011407711613050422), 342 Params: &types.LogNormalModelParams{ 343 Mu: num.DecimalZero(), 344 R: num.DecimalFromFloat(0.016), 345 Sigma: num.DecimalFromFloat(20), 346 }, 347 }, 348 } 349 350 lpparty := "lp-party-1" 351 lpparty2 := "lp-party-2" 352 353 tm := newTestMarket(t, now).Run(ctx, mktCfg) 354 tm.StartOpeningAuction(). 355 WithAccountAndAmount(lpparty, 500000000000). 356 WithAccountAndAmount(lpparty2, 500000000000) 357 358 tm.now = now 359 tm.market.OnTick(ctx, now) 360 361 // Add a LPSubmission 362 // this is a log of stake, enough to cover all 363 // the required stake for the market 364 lpSubmission := &types.LiquidityProvisionSubmission{ 365 MarketID: tm.market.GetID(), 366 CommitmentAmount: num.NewUint(70000), 367 Fee: num.DecimalFromFloat(0.5), 368 Reference: "ref-lp-submission-1", 369 } 370 371 // submit our lp 372 tm.events = nil 373 require.NoError(t, 374 tm.market.SubmitLiquidityProvision( 375 ctx, lpSubmission, lpparty, vgcrypto.RandomHash()), 376 ) 377 378 tm.market.OnEpochEvent(ctx, types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_START}) 379 380 t.Run("current liquidity fee is 0.5", func(t *testing.T) { 381 // First collect all the orders events 382 found := proto.Market{} 383 for _, e := range tm.events { 384 switch evt := e.(type) { 385 case *events.MarketUpdated: 386 found = evt.Market() 387 } 388 } 389 390 assert.Equal(t, found.Fees.Factors.LiquidityFee, "0.5") 391 }) 392 393 tm.EndOpeningAuction(t, auctionEnd, false) 394 395 // now we submit a second LP, with a lower fee, 396 // but we still need the first LP to cover liquidity 397 // so its fee is selected 398 lpSubmission2 := &types.LiquidityProvisionSubmission{ 399 MarketID: tm.market.GetID(), 400 CommitmentAmount: num.NewUint(20000), 401 Fee: num.DecimalFromFloat(0.1), 402 Reference: "ref-lp-submission-1", 403 } 404 405 // submit our lp 406 tm.events = nil 407 require.NoError(t, 408 tm.market.SubmitLiquidityProvision( 409 ctx, lpSubmission2, lpparty2, vgcrypto.RandomHash()), 410 ) 411 412 tm.market.OnEpochEvent(ctx, types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_START}) 413 414 t.Run("current liquidity fee is still 0.5", func(t *testing.T) { 415 // First collect all the orders events 416 var found *proto.Market 417 for _, e := range tm.events { 418 switch evt := e.(type) { 419 case *events.MarketUpdated: 420 mkt := evt.Market() 421 found = &mkt 422 } 423 } 424 425 // no update to the liquidity fee 426 assert.Nil(t, found) 427 }) 428 }) 429 430 // Liquidity fee must be updated when new LP submissions are added or existing ones 431 // removed. 432 t.Run("check that LP fee is correct after changes", func(t *testing.T) { 433 t.Skip() 434 now := time.Unix(10, 0) 435 tm := getTestMarket(t, now, nil, nil) 436 ctx := context.Background() 437 438 // Create a new party account with very little funding 439 addAccountWithAmount(tm, "party-A", 7000) 440 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 441 442 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 443 tm.mas.AuctionStarted(ctx, now) 444 tm.market.EnterAuction(ctx) 445 446 // We shouldn't have a liquidity fee yet 447 // TODO assert.Equal(t, 0.0, tm.market.GetLiquidityFee()) 448 449 // Submitting a correct entry 450 lps := &types.LiquidityProvisionSubmission{ 451 Fee: num.DecimalFromFloat(0.01), 452 MarketID: tm.market.GetID(), 453 CommitmentAmount: num.NewUint(1000), 454 } 455 456 err := tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 457 require.NoError(t, err) 458 459 tm.market.OnEpochEvent(ctx, types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_START}) 460 461 // Update the fee 462 lpa := &types.LiquidityProvisionAmendment{ 463 Fee: num.DecimalFromFloat(0.5), 464 } 465 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 466 require.NoError(t, err) 467 468 // Check the fee is correct 469 // TODO assert.Equal(t, 0.5, tm.market.GetLiquidityFee()) 470 }) 471 472 t.Run("check that LP commitment reduction is prevented correctly", func(t *testing.T) { 473 t.Skip() 474 now := time.Unix(10, 0) 475 tm := getTestMarket(t, now, nil, nil) 476 ctx := context.Background() 477 478 // Create a new party account with very little funding 479 addAccountWithAmount(tm, "party-A", 10000000000) 480 addAccountWithAmount(tm, "party-B", 10000000) 481 addAccountWithAmount(tm, "party-C", 10000000) 482 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 483 484 // Start the opening auction 485 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 486 tm.mas.AuctionStarted(ctx, now) 487 tm.market.EnterAuction(ctx) 488 489 // Create some normal orders to set the reference prices 490 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-B", 10, 10) 491 o1conf, err := tm.market.SubmitOrder(ctx, o1) 492 require.NotNil(t, o1conf) 493 require.NoError(t, err) 494 495 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-C", 2, 10) 496 o2conf, err := tm.market.SubmitOrder(ctx, o2) 497 require.NotNil(t, o2conf) 498 require.NoError(t, err) 499 500 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order03", types.SideSell, "party-C", 1, 20) 501 o3conf, err := tm.market.SubmitOrder(ctx, o3) 502 require.NotNil(t, o3conf) 503 require.NoError(t, err) 504 505 o4 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order04", types.SideBuy, "party-C", 1, 9) 506 o4conf, err := tm.market.SubmitOrder(ctx, o4) 507 require.NotNil(t, o4conf) 508 require.NoError(t, err) 509 510 // Leave auction 511 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 512 // mark price is set at 10, orders on book 513 514 // Submitting a correct entry 515 lps := &types.LiquidityProvisionSubmission{ 516 Fee: num.DecimalFromFloat(0.01), 517 MarketID: tm.market.GetID(), 518 CommitmentAmount: num.NewUint(1000), 519 } 520 521 err = tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 522 require.NoError(t, err) 523 assert.Equal(t, 1, tm.market.GetLPSCount()) 524 525 // Try to reduce our commitment to below the minimum level 526 lpa := &types.LiquidityProvisionAmendment{ 527 Fee: num.DecimalFromFloat(0.01), 528 MarketID: tm.market.GetID(), 529 CommitmentAmount: num.NewUint(1), 530 } 531 532 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 533 require.Error(t, err) 534 assert.Equal(t, 1, tm.market.GetLPSCount()) 535 }) 536 537 t.Run("check that changing LP during auction works", func(t *testing.T) { 538 t.Skip() 539 tm := getTestMarket(t, now, nil, nil) 540 541 // Create a new party account with very little funding 542 addAccountWithAmount(tm, "party-A", 7000) 543 addAccountWithAmount(tm, "party-B", 10000000) 544 addAccountWithAmount(tm, "party-C", 10000000) 545 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 546 547 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 548 tm.mas.AuctionStarted(ctx, now) 549 tm.market.EnterAuction(ctx) 550 551 // Create some normal orders to set the reference prices 552 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-B", 10, 10) 553 o1conf, err := tm.market.SubmitOrder(ctx, o1) 554 require.NotNil(t, o1conf) 555 require.NoError(t, err) 556 557 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-C", 2, 10) 558 o2conf, err := tm.market.SubmitOrder(ctx, o2) 559 require.NotNil(t, o2conf) 560 require.NoError(t, err) 561 562 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order03", types.SideSell, "party-C", 1, 20) 563 o3conf, err := tm.market.SubmitOrder(ctx, o3) 564 require.NotNil(t, o3conf) 565 require.NoError(t, err) 566 567 o31 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order031", types.SideSell, "party-C", 1, 30) 568 o31conf, err := tm.market.SubmitOrder(ctx, o31) 569 require.NotNil(t, o31conf) 570 require.NoError(t, err) 571 572 // Submitting a correct entry 573 lps := &types.LiquidityProvisionSubmission{ 574 Fee: num.DecimalFromFloat(0.01), 575 MarketID: tm.market.GetID(), 576 CommitmentAmount: num.NewUint(1000), 577 } 578 579 err = tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 580 require.NoError(t, err) 581 require.Equal(t, types.LiquidityProvisionStatusPending.String(), tm.market.GetLPSState("party-A").String()) 582 assert.Equal(t, 0, tm.market.GetPeggedOrderCount()) 583 584 // Check we have the right amount of bond balance 585 assert.Equal(t, num.NewUint(1000), tm.market.GetBondAccountBalance(ctx, "party-A", tm.market.GetID(), tm.asset)) 586 587 // Amend the commitment 588 lpa := &types.LiquidityProvisionAmendment{ 589 Fee: lps.Fee, 590 MarketID: lps.MarketID, 591 CommitmentAmount: num.NewUint(2000), 592 } 593 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 594 require.NoError(t, err) 595 596 tm.market.OnEpochEvent(ctx, types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_START}) 597 598 // Check we have the right amount of bond balance 599 assert.Equal(t, num.NewUint(2000), tm.market.GetBondAccountBalance(ctx, "party-A", tm.market.GetID(), tm.asset)) 600 601 // Amend the commitment 602 lpa.CommitmentAmount = num.NewUint(500) 603 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 604 require.NoError(t, err) 605 606 // Check we have the right amount of bond balance 607 assert.Equal(t, num.NewUint(500), tm.market.GetBondAccountBalance(ctx, "party-A", tm.market.GetID(), tm.asset)) 608 609 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 610 require.NoError(t, err) 611 assert.Equal(t, 0, tm.market.GetPeggedOrderCount()) 612 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 613 }) 614 615 // Check that we are unable to directly cancel or amend a pegged order that was 616 // created by the LP system. 617 t.Run("check that it is not possible to cancel or amend LP order", func(t *testing.T) { 618 t.Skip() 619 now := time.Unix(10, 0) 620 tm := getTestMarket(t, now, nil, nil) 621 ctx := context.Background() 622 623 // Create a new party account with very little funding 624 addAccountWithAmount(tm, "party-A", 10000000) 625 addAccountWithAmount(tm, "party-B", 10000000) 626 addAccountWithAmount(tm, "party-C", 10000000) 627 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 628 629 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 630 tm.mas.AuctionStarted(ctx, now) 631 tm.market.EnterAuction(ctx) 632 633 // Create some normal orders to set the reference prices 634 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-B", 10, 10) 635 o1conf, err := tm.market.SubmitOrder(ctx, o1) 636 require.NotNil(t, o1conf) 637 require.NoError(t, err) 638 639 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-C", 2, 10) 640 o2conf, err := tm.market.SubmitOrder(ctx, o2) 641 require.NotNil(t, o2conf) 642 require.NoError(t, err) 643 644 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order03", types.SideSell, "party-C", 1, 20) 645 o3conf, err := tm.market.SubmitOrder(ctx, o3) 646 require.NotNil(t, o3conf) 647 require.NoError(t, err) 648 649 // Submitting a correct entry 650 lps := &types.LiquidityProvisionSubmission{ 651 Fee: num.DecimalFromFloat(0.01), 652 MarketID: tm.market.GetID(), 653 CommitmentAmount: num.NewUint(1000), 654 } 655 656 err = tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 657 require.NoError(t, err) 658 659 // Leave auction 660 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 661 662 // Check we have an accepted LP submission 663 assert.Equal(t, 1, tm.market.GetLPSCount()) 664 665 // Check we have the right number of live orders 666 assert.Equal(t, int64(6), tm.market.GetOrdersOnBookCount()) 667 668 // FIXME(): REDO THIS TEST 669 // Attempt to cancel one of the pegged orders and it is rejected 670 // orders := tm.market.GetPeggedOrders("party-A") 671 // assert.GreaterOrEqual(t, len(orders), 0) 672 673 // cancelConf, err := tm.market.CancelOrder(ctx, "party-A", orders[0].Id) 674 // require.Nil(t, cancelConf) 675 // require.Error(t, err) 676 // assert.Equal(t, types.OrderError_ORDER_ERROR_EDIT_NOT_ALLOWED, err) 677 678 // // Attempt to amend one of the pegged orders 679 // amend := &commandspb.OrderAmendment{OrderId: orders[0].Id, 680 // MarketId: orders[0].MarketId, 681 // SizeDelta: +5} 682 // amendConf, err := tm.market.AmendOrder(ctx, amend, orders[0].PartyId) 683 // require.Error(t, err) 684 // require.Nil(t, amendConf) 685 // assert.Equal(t, types.OrderError_ORDER_ERROR_EDIT_NOT_ALLOWED, err) 686 }) 687 688 // If we submit a valid LP submission but then try ot alter it to something non valid 689 // the amendment should be rejected and the original submission is still valid. 690 t.Run("check that failed amend does not break existing LP", func(t *testing.T) { 691 t.Skip() 692 tm := getTestMarket(t, now, nil, nil) 693 694 // Create a new party account with very little funding 695 addAccountWithAmount(tm, "party-A", 7000) 696 addAccountWithAmount(tm, "party-B", 10000000) 697 addAccountWithAmount(tm, "party-C", 10000000) 698 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 699 700 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 701 tm.mas.AuctionStarted(ctx, now) 702 tm.market.EnterAuction(ctx) 703 704 // Submitting a correct entry 705 lps := &types.LiquidityProvisionSubmission{ 706 Fee: num.DecimalFromFloat(0.01), 707 MarketID: tm.market.GetID(), 708 CommitmentAmount: num.NewUint(1000), 709 } 710 711 err := tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 712 require.NoError(t, err) 713 require.Equal(t, types.LiquidityProvisionStatusPending.String(), tm.market.GetLPSState("party-A").String()) 714 assert.Equal(t, 0, tm.market.GetPeggedOrderCount()) 715 assert.Equal(t, num.NewUint(1000), tm.market.GetBondAccountBalance(ctx, "party-A", tm.market.GetID(), tm.asset)) 716 717 // Now attempt to amend the LP submission with empty fee 718 lpa := &types.LiquidityProvisionAmendment{ 719 MarketID: lps.MarketID, 720 CommitmentAmount: lps.CommitmentAmount, 721 } 722 723 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 724 require.NoError(t, err) 725 726 // Now attempt to amend the LP submission with empty fee and commitment amount 727 lpa = &types.LiquidityProvisionAmendment{ 728 MarketID: lps.MarketID, 729 } 730 731 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 732 require.NoError(t, err) 733 734 // Now attempt to amend the LP submission with empty buys 735 lpa = &types.LiquidityProvisionAmendment{ 736 Fee: lps.Fee, 737 MarketID: lps.MarketID, 738 CommitmentAmount: lps.CommitmentAmount, 739 } 740 741 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 742 require.NoError(t, err) 743 744 // Now attempt to amend the LP submission with no changes with nil buys and nil sells 745 lpa = &types.LiquidityProvisionAmendment{ 746 Fee: num.DecimalZero(), 747 MarketID: lps.MarketID, 748 CommitmentAmount: num.UintZero(), 749 } 750 751 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 752 require.EqualError(t, err, "empty liquidity provision amendment content") 753 754 // Now attempt to amend the LP submission with no changes with sells and buys empty lists 755 lpa = &types.LiquidityProvisionAmendment{ 756 Fee: num.DecimalZero(), 757 MarketID: lps.MarketID, 758 CommitmentAmount: num.UintZero(), 759 } 760 761 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 762 require.EqualError(t, err, "empty liquidity provision amendment content") 763 764 // Check that the original LP submission is still working fine 765 require.Equal(t, types.LiquidityProvisionStatusPending.String(), tm.market.GetLPSState("party-A").String()) 766 }) 767 768 // Reference must be updated when LP submissions are amended. 769 t.Run("check reference is correct after changes", func(t *testing.T) { 770 t.Skip() 771 tm := getTestMarket(t, now, nil, nil) 772 773 // Create a new party account with very little funding 774 addAccountWithAmount(tm, "party-A", 7000) 775 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 776 777 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 778 tm.mas.AuctionStarted(ctx, now) 779 tm.market.EnterAuction(ctx) 780 781 // Submitting a correct entry 782 lps := &types.LiquidityProvisionSubmission{ 783 Fee: num.DecimalFromFloat(0.01), 784 MarketID: tm.market.GetID(), 785 CommitmentAmount: num.NewUint(1000), 786 Reference: "ref-lp-1", 787 } 788 789 err := tm.market.SubmitLiquidityProvision(ctx, lps, "party-A", vgcrypto.RandomHash()) 790 require.NoError(t, err) 791 792 // Update the fee 793 lpa := &types.LiquidityProvisionAmendment{ 794 Fee: num.DecimalFromFloat(0.2), 795 } 796 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 797 require.NoError(t, err) 798 799 // Update the fee again with a new reference 800 lpa = &types.LiquidityProvisionAmendment{ 801 Fee: num.DecimalFromFloat(0.5), 802 Reference: "ref-lp-2", 803 } 804 err = tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 805 require.NoError(t, err) 806 807 t.Run("expect LP references", func(t *testing.T) { 808 // First collect all the lp events 809 found := map[string]*proto.LiquidityProvision{} 810 for _, e := range tm.events { 811 switch evt := e.(type) { 812 case *events.LiquidityProvision: 813 lp := evt.LiquidityProvision() 814 found[lp.Fee] = lp 815 } 816 } 817 expectedStatus := map[string]string{"0.01": "ref-lp-1", "0.2": "ref-lp-1", "0.5": "ref-lp-2"} 818 require.Len(t, found, len(expectedStatus)) 819 820 for k, v := range expectedStatus { 821 assert.Equal(t, v, found[k].Reference) 822 } 823 }) 824 }) 825 826 t.Run("should reject LP amendment if no current LP", func(t *testing.T) { 827 t.Skip() 828 now := time.Unix(10, 0) 829 tm := getTestMarket(t, now, nil, nil) 830 ctx := context.Background() 831 832 // Create a new party account with very little funding 833 addAccountWithAmount(tm, "party-A", 7000) 834 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 835 836 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 837 tm.mas.AuctionStarted(ctx, now) 838 tm.market.EnterAuction(ctx) 839 840 // Try to update the fee 841 lpa := &types.LiquidityProvisionAmendment{ 842 Fee: num.DecimalFromFloat(0.5), 843 } 844 err := tm.market.AmendLiquidityProvision(ctx, lpa, "party-A", vgcrypto.RandomHash()) 845 require.EqualError(t, err, "party is not a liquidity provider") 846 }) 847 848 t.Run("should reject LP cancellation if no current LP", func(t *testing.T) { 849 t.Skip() 850 tm := getTestMarket(t, now, nil, nil) 851 852 // Create a new party account with very little funding 853 addAccountWithAmount(tm, "party-A", 7000) 854 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 855 856 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 857 tm.mas.AuctionStarted(ctx, now) 858 tm.market.EnterAuction(ctx) 859 860 lpc := &types.LiquidityProvisionCancellation{ 861 MarketID: tm.market.GetID(), 862 } 863 err := tm.market.CancelLiquidityProvision(ctx, lpc, "party-A") 864 require.EqualError(t, err, "party is not a liquidity provider") 865 }) 866 }