code.vegaprotocol.io/vega@v0.79.0/core/execution/common/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 common_test 17 18 import ( 19 "context" 20 "encoding/hex" 21 "testing" 22 "time" 23 24 bmocks "code.vegaprotocol.io/vega/core/broker/mocks" 25 "code.vegaprotocol.io/vega/core/collateral" 26 cmocks "code.vegaprotocol.io/vega/core/collateral/mocks" 27 "code.vegaprotocol.io/vega/core/execution/common" 28 "code.vegaprotocol.io/vega/core/execution/common/mocks" 29 ammcmocks "code.vegaprotocol.io/vega/core/execution/common/mocks_amm" 30 "code.vegaprotocol.io/vega/core/fee" 31 "code.vegaprotocol.io/vega/core/liquidity/v2" 32 lmocks "code.vegaprotocol.io/vega/core/liquidity/v2/mocks" 33 "code.vegaprotocol.io/vega/core/types" 34 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 35 "code.vegaprotocol.io/vega/libs/num" 36 "code.vegaprotocol.io/vega/logging" 37 38 "github.com/golang/mock/gomock" 39 "github.com/stretchr/testify/assert" 40 ) 41 42 type marketLiquidityTest struct { 43 ctrl *gomock.Controller 44 ctx context.Context 45 marketID string 46 asset string 47 48 marketLiquidity *common.MarketLiquidity 49 50 liquidityEngine *mocks.MockLiquidityEngine 51 collateralEngine common.Collateral 52 epochEngine *mocks.MockEpochEngine 53 equityShares *mocks.MockEquityLikeShares 54 broker *bmocks.MockBroker 55 orderBook *lmocks.MockOrderBook 56 timeService *cmocks.MockTimeService 57 amm *ammcmocks.MockAMM 58 } 59 60 func newMarketLiquidity(t *testing.T) *marketLiquidityTest { 61 t.Helper() 62 63 ctrl := gomock.NewController(t) 64 log := logging.NewTestLogger() 65 66 liquidityEngine := mocks.NewMockLiquidityEngine(ctrl) 67 epochEngine := mocks.NewMockEpochEngine(ctrl) 68 equityShares := mocks.NewMockEquityLikeShares(ctrl) 69 broker := bmocks.NewMockBroker(ctrl) 70 orderBook := lmocks.NewMockOrderBook(ctrl) 71 timeService := cmocks.NewMockTimeService(ctrl) 72 73 collateralEngine := collateral.New(log, collateral.NewDefaultConfig(), timeService, broker) 74 75 marketID := "market-1" 76 settlementAsset := "USDT" 77 78 fees, _ := fee.New(log, fee.NewDefaultConfig(), types.Fees{Factors: &types.FeeFactors{}}, settlementAsset, num.DecimalOne()) 79 80 liquidityEngine.EXPECT().RegisterAllocatedFeesPerParty(gomock.Any()).AnyTimes() 81 liquidityEngine.EXPECT().PaidLiquidityFeesStats().Return(types.NewLiquidityFeeStats()).AnyTimes() 82 epochEngine.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).AnyTimes() 83 84 teams := mocks.NewMockTeams(ctrl) 85 bc := mocks.NewMockAccountBalanceChecker(ctrl) 86 marketTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) 87 epochEngine.NotifyOnEpoch(marketTracker.OnEpochEvent, marketTracker.OnEpochRestore) 88 amm := ammcmocks.NewMockAMM(ctrl) 89 90 marketLiquidity := common.NewMarketLiquidity( 91 log, 92 liquidityEngine, 93 collateralEngine, 94 broker, 95 orderBook, 96 equityShares, 97 marketTracker, 98 fees, 99 common.SpotMarketType, 100 marketID, 101 settlementAsset, 102 num.DecimalOne(), 103 num.NewDecimalFromFloat(0.5), 104 amm, 105 ) 106 107 marketLiquidity.OnMinLPStakeQuantumMultiple(num.DecimalOne()) 108 marketLiquidity.OnEarlyExitPenalty(num.DecimalOne()) 109 110 return &marketLiquidityTest{ 111 ctrl: ctrl, 112 marketID: marketID, 113 asset: settlementAsset, 114 marketLiquidity: marketLiquidity, 115 liquidityEngine: liquidityEngine, 116 collateralEngine: collateralEngine, 117 equityShares: equityShares, 118 epochEngine: epochEngine, 119 broker: broker, 120 orderBook: orderBook, 121 timeService: timeService, 122 amm: amm, 123 ctx: context.Background(), 124 } 125 } 126 127 func createPartyAndPayLiquidityFee(t *testing.T, amount *num.Uint, testLiquidity *marketLiquidityTest) { 128 t.Helper() 129 130 tradingParty := "party-1" 131 _, err := testLiquidity.collateralEngine.CreatePartyGeneralAccount(testLiquidity.ctx, tradingParty, testLiquidity.asset) 132 assert.NoError(t, err) 133 134 _, err = testLiquidity.collateralEngine.Deposit(testLiquidity.ctx, tradingParty, testLiquidity.asset, amount) 135 assert.NoError(t, err) 136 137 _, err = testLiquidity.collateralEngine.GetPartyGeneralAccount(tradingParty, testLiquidity.asset) 138 assert.NoError(t, err) 139 140 transfer := testLiquidity.marketLiquidity.NewTransfer(tradingParty, types.TransferTypeLiquidityFeePay, amount.Clone()) 141 142 _, err = testLiquidity.collateralEngine.TransferSpotFees( 143 testLiquidity.ctx, 144 testLiquidity.marketID, 145 testLiquidity.asset, 146 common.NewFeeTransfer([]*types.Transfer{transfer}, nil), types.AccountTypeGeneral, 147 ) 148 assert.NoError(t, err) 149 } 150 151 func TestLiquidityProvisionsFeeDistribution(t *testing.T) { 152 testLiquidity := newMarketLiquidity(t) 153 // set fee factor to 1, so fees are not paid out based on score. 154 testLiquidity.marketLiquidity.SetELSFeeFraction(num.DecimalOne()) 155 156 weightsPerLP := map[string]num.Decimal{ 157 "lp-1": num.NewDecimalFromFloat(0.008764241896), 158 "lp-2": num.NewDecimalFromFloat(0.0008764241895), 159 "lp-3": num.NewDecimalFromFloat(0.0175284838), 160 "lp-4": num.NewDecimalFromFloat(0.03505689996), 161 "lp-5": num.NewDecimalFromFloat(0.061349693), 162 "lp-6": num.NewDecimalFromFloat(0.876424189), 163 } 164 165 expectedAllocatedFess := map[string]num.Uint{ 166 "lp-1": *num.NewUint(1000), 167 "lp-2": *num.NewUint(100), 168 "lp-3": *num.NewUint(2000), 169 "lp-4": *num.NewUint(4000), 170 "lp-5": *num.NewUint(7000), 171 "lp-6": *num.NewUint(100000), 172 } 173 174 expectedDistributedFess := map[string]num.Uint{ 175 "lp-1": *num.NewUint(13923), 176 "lp-2": *num.NewUint(1322), 177 "lp-3": *num.NewUint(25061), 178 "lp-4": *num.NewUint(44553), 179 // expected fee is 29238 but the party will be selected to receive reaming distribution account funds (3). 180 "lp-5": *num.NewUint(29241), 181 "lp-6": *num.NewUint(0), 182 } 183 184 keys := []string{"lp-1", "lp-2", "lp-3", "lp-4", "lp-5", "lp-6"} 185 186 ctx := context.Background() 187 188 testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes() 189 testLiquidity.orderBook.EXPECT().GetBestStaticAskPrice().Return(num.NewUint(100), nil).AnyTimes() 190 testLiquidity.orderBook.EXPECT().GetBestStaticBidPrice().Return(num.NewUint(100), nil).AnyTimes() 191 testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(map[string]common.AMMPool{}).AnyTimes() 192 193 testLiquidity.liquidityEngine.EXPECT().UpdatePartyCommitment(gomock.Any(), gomock.Any()).DoAndReturn( 194 func(partyID string, amount *num.Uint) (*types.LiquidityProvision, error) { 195 return &types.LiquidityProvision{ 196 Party: partyID, 197 CommitmentAmount: amount.Clone(), 198 }, nil 199 }).AnyTimes() 200 201 // enable asset first. 202 err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{ 203 ID: testLiquidity.asset, 204 Details: &types.AssetDetails{ 205 Name: testLiquidity.asset, 206 Symbol: testLiquidity.asset, 207 Decimals: 0, 208 Source: types.AssetDetailsErc20{ 209 ERC20: &types.ERC20{ 210 ContractAddress: "addrs", 211 ChainID: "1", 212 }, 213 }, 214 }, 215 }) 216 assert.NoError(t, err) 217 218 // create all required accounts for spot market. 219 err = testLiquidity.collateralEngine.CreateSpotMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset) 220 assert.NoError(t, err) 221 222 testLiquidity.liquidityEngine.EXPECT(). 223 SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 224 AnyTimes() 225 226 testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes() 227 one := num.UintOne() 228 testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes() 229 testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 230 231 testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time { 232 return time.Now() 233 }).AnyTimes() 234 235 decimalOne := num.DecimalOne() 236 uintOne := num.UintOne() 237 commitmentAmount := num.NewUint(10) 238 scoresPerLP := map[string]num.Decimal{} 239 provisionsPerParty := map[string]*types.LiquidityProvision{} 240 241 // create liquidity providers accounts and submit provision. 242 for provider := range weightsPerLP { 243 // set score to one. 244 scoresPerLP[provider] = decimalOne 245 246 // create providers general account and deposit funds into it. 247 _, err := testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset) 248 assert.NoError(t, err) 249 250 _, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount) 251 assert.NoError(t, err) 252 253 // submit the provision. 254 provision := &types.LiquidityProvisionSubmission{ 255 MarketID: testLiquidity.marketID, 256 CommitmentAmount: commitmentAmount, 257 Reference: provider, 258 } 259 260 deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider))) 261 err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider, 262 deterministicID, types.MarketStateActive) 263 assert.NoError(t, err) 264 265 // setup provision per party. 266 provisionsPerParty[provider] = &types.LiquidityProvision{ 267 Party: provider, 268 CommitmentAmount: provision.CommitmentAmount.Clone(), 269 } 270 } 271 272 // create party and make it pay liquidity fee. 273 createPartyAndPayLiquidityFee(t, num.NewUint(114101), testLiquidity) 274 275 testLiquidity.liquidityEngine.EXPECT().ProvisionsPerParty().DoAndReturn(func() liquidity.ProvisionsPerParty { 276 return provisionsPerParty 277 }).AnyTimes() 278 279 testLiquidity.liquidityEngine.EXPECT().ResetSLAEpoch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 280 281 // start epoch. 282 lastDistributionStep := time.Now() 283 now := lastDistributionStep.Add(time.Second * 5) 284 testLiquidity.liquidityEngine.EXPECT().ReadyForFeesAllocation(gomock.Any()).Return(true) 285 286 testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne) 287 288 testLiquidity.liquidityEngine.EXPECT().ResetAverageLiquidityScores().AnyTimes() 289 testLiquidity.liquidityEngine.EXPECT().ResetFeeAllocationPeriod(gomock.Any()).AnyTimes() 290 291 testLiquidity.equityShares.EXPECT().AllShares().DoAndReturn(func() map[string]num.Decimal { 292 return weightsPerLP 293 }).AnyTimes() 294 295 testLiquidity.liquidityEngine.EXPECT().GetAverageLiquidityScores().DoAndReturn(func() map[string]num.Decimal { 296 return scoresPerLP 297 }) 298 299 // trigger a time tick - this should start allocation fees to LP fee accounts. 300 testLiquidity.marketLiquidity.OnTick(ctx, now) 301 302 for _, provider := range keys { 303 acc, err := testLiquidity.collateralEngine.GetPartyLiquidityFeeAccount( 304 testLiquidity.marketID, 305 provider, 306 testLiquidity.asset, 307 ) 308 assert.NoError(t, err) 309 310 expected := expectedAllocatedFess[provider] 311 assert.True(t, expected.EQ(acc.Balance)) 312 } 313 314 zeroPointFive := num.NewDecimalFromFloat(0.5) 315 expectedSLAPenalties := map[string]*liquidity.SlaPenalty{ 316 "lp-1": { 317 Fee: num.NewDecimalFromFloat(0), 318 Bond: zeroPointFive, 319 }, 320 "lp-2": { 321 Fee: num.NewDecimalFromFloat(0.05), 322 Bond: zeroPointFive, 323 }, 324 "lp-3": { 325 Fee: num.NewDecimalFromFloat(0.1), 326 Bond: zeroPointFive, 327 }, 328 "lp-4": { 329 Fee: num.NewDecimalFromFloat(0.2), 330 Bond: zeroPointFive, 331 }, 332 "lp-5": { 333 Fee: num.NewDecimalFromFloat(0.7), 334 Bond: zeroPointFive, 335 }, 336 "lp-6": { 337 Fee: num.NewDecimalFromFloat(1), 338 Bond: zeroPointFive, 339 }, 340 } 341 342 testLiquidity.liquidityEngine.EXPECT().CalculateSLAPenalties(gomock.Any()).DoAndReturn( 343 func(_ time.Time) liquidity.SlaPenalties { 344 return liquidity.SlaPenalties{ 345 PenaltiesPerParty: expectedSLAPenalties, 346 } 347 }, 348 ) 349 350 testLiquidity.liquidityEngine.EXPECT(). 351 LiquidityProvisionByPartyID(gomock.Any()). 352 DoAndReturn(func(party string) *types.LiquidityProvision { 353 return &types.LiquidityProvision{ 354 ID: party, 355 Party: party, 356 CommitmentAmount: commitmentAmount, 357 } 358 }).AnyTimes() 359 360 // end epoch - this should trigger the SLA fees distribution. 361 testLiquidity.marketLiquidity.OnEpochEnd(testLiquidity.ctx, now, types.Epoch{}) 362 363 for _, provider := range keys { 364 generalAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount( 365 provider, 366 testLiquidity.asset, 367 ) 368 assert.NoError(t, err) 369 370 expectedFee := expectedDistributedFess[provider] 371 assert.Truef(t, expectedFee.EQ(generalAcc.Balance), 372 "party %s general account balance is %s, expected: %s", provider, generalAcc.Balance, expectedFee) 373 374 bondAcc, err := testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset) 375 assert.NoError(t, err) 376 377 penalty := expectedSLAPenalties[provider] 378 379 num.UintFromDecimal(penalty.Bond.Mul(commitmentAmount.ToDecimal())) 380 expectedBondAccount, _ := num.UintFromDecimal(penalty.Bond.Mul(commitmentAmount.ToDecimal())) 381 assert.True(t, bondAcc.Balance.EQ(expectedBondAccount)) 382 } 383 384 acc, err := testLiquidity.collateralEngine.GetOrCreateLiquidityFeesBonusDistributionAccount( 385 ctx, 386 testLiquidity.marketID, 387 testLiquidity.asset, 388 ) 389 assert.NoError(t, err) 390 assert.True(t, acc.Balance.EQ(num.UintZero())) 391 392 testLiquidity.equityShares.EXPECT().SetPartyStake(gomock.Any(), gomock.Any()).AnyTimes() 393 testLiquidity.equityShares.EXPECT().AllShares().AnyTimes() 394 testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne) 395 } 396 397 func TestLiquidityProvisionsWithPoolsFeeDistribution(t *testing.T) { 398 testLiquidity := newMarketLiquidity(t) 399 // set fee factor to 1, so fees are not paid out based on score. 400 testLiquidity.marketLiquidity.SetELSFeeFraction(num.DecimalOne()) 401 402 weightsPerLP := map[string]num.Decimal{ 403 "lp-1": num.NewDecimalFromFloat(0.008764241896), 404 "lp-2": num.NewDecimalFromFloat(0.0008764241895), 405 "lp-3": num.NewDecimalFromFloat(0.0175284838), 406 "lp-4": num.NewDecimalFromFloat(0.03505689996), 407 "lp-5": num.NewDecimalFromFloat(0.061349693), 408 "lp-6": num.NewDecimalFromFloat(0.2921413963), 409 "pool-party-1": num.NewDecimalFromFloat(0.2921413963), 410 "pool-party-2": num.NewDecimalFromFloat(0.2921413963), 411 } 412 413 expectedAllocatedFess := map[string]num.Uint{ 414 "lp-1": *num.NewUint(1000), 415 "lp-2": *num.NewUint(100), 416 "lp-3": *num.NewUint(2000), 417 "lp-4": *num.NewUint(4000), 418 "lp-5": *num.NewUint(7000), 419 "lp-6": *num.NewUint(33333), 420 "pool-party-1": *num.NewUint(33333), 421 "pool-party-2": *num.NewUint(33333), 422 } 423 424 expectedDistributedFess := map[string]num.Uint{ 425 "lp-1": *num.NewUint(1527), 426 "lp-2": *num.NewUint(144), 427 "lp-3": *num.NewUint(2743), 428 "lp-4": *num.NewUint(4877), 429 "lp-5": *num.NewUint(3200), 430 "lp-6": *num.NewUint(0), 431 "pool-party-1": *num.NewUint(50804), 432 "pool-party-2": *num.NewUint(50804), 433 } 434 435 poolsPartyIDs := []string{"pool-party-1", "pool-party-2"} 436 keys := append([]string{"lp-1", "lp-2", "lp-3", "lp-4", "lp-5", "lp-6"}, poolsPartyIDs...) 437 438 ctx := context.Background() 439 440 testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes() 441 442 testLiquidity.orderBook.EXPECT().GetBestStaticAskPrice().Return(num.NewUint(100), nil).AnyTimes() 443 testLiquidity.orderBook.EXPECT().GetBestStaticBidPrice().Return(num.NewUint(100), nil).AnyTimes() 444 445 poolMock := ammcmocks.NewMockAMMPool(testLiquidity.ctrl) 446 ammPools := map[string]common.AMMPool{ 447 "pool-party-1": poolMock, 448 "pool-party-2": poolMock, 449 } 450 451 testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(ammPools).AnyTimes() 452 453 testLiquidity.liquidityEngine.EXPECT().UpdatePartyCommitment(gomock.Any(), gomock.Any()).DoAndReturn( 454 func(partyID string, amount *num.Uint) (*types.LiquidityProvision, error) { 455 return &types.LiquidityProvision{ 456 Party: partyID, 457 CommitmentAmount: amount.Clone(), 458 }, nil 459 }).AnyTimes() 460 461 // enable asset first. 462 err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{ 463 ID: testLiquidity.asset, 464 Details: &types.AssetDetails{ 465 Name: testLiquidity.asset, 466 Symbol: testLiquidity.asset, 467 Decimals: 0, 468 Source: types.AssetDetailsErc20{ 469 ERC20: &types.ERC20{ 470 ContractAddress: "addrs", 471 }, 472 }, 473 }, 474 }) 475 assert.NoError(t, err) 476 477 for _, partyID := range poolsPartyIDs { 478 // create pool party general account. 479 _, err = testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, partyID, testLiquidity.asset) 480 assert.NoError(t, err) 481 482 // create pool party liquidity fee account. 483 _, err := testLiquidity.collateralEngine.GetOrCreatePartyLiquidityFeeAccount(ctx, partyID, testLiquidity.marketID, testLiquidity.asset) 484 assert.NoError(t, err) 485 } 486 487 // create all required accounts for spot market. 488 err = testLiquidity.collateralEngine.CreateSpotMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset) 489 assert.NoError(t, err) 490 491 testLiquidity.liquidityEngine.EXPECT(). 492 SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 493 AnyTimes() 494 495 testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes() 496 one := num.UintOne() 497 testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes() 498 testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 499 500 testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time { 501 return time.Now() 502 }).AnyTimes() 503 504 decimalOne := num.DecimalOne() 505 uintOne := num.UintOne() 506 commitmentAmount := num.NewUint(10) 507 scoresPerLP := map[string]num.Decimal{} 508 provisionsPerParty := map[string]*types.LiquidityProvision{} 509 510 // create liquidity providers accounts and submit provision. 511 for provider := range weightsPerLP { 512 // set score to one. 513 scoresPerLP[provider] = decimalOne 514 515 // create providers general account and deposit funds into it. 516 _, err := testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset) 517 assert.NoError(t, err) 518 519 _, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount) 520 assert.NoError(t, err) 521 522 // submit the provision. 523 provision := &types.LiquidityProvisionSubmission{ 524 MarketID: testLiquidity.marketID, 525 CommitmentAmount: commitmentAmount, 526 Reference: provider, 527 } 528 529 deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider))) 530 err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider, 531 deterministicID, types.MarketStateActive) 532 assert.NoError(t, err) 533 534 // setup provision per party. 535 provisionsPerParty[provider] = &types.LiquidityProvision{ 536 Party: provider, 537 CommitmentAmount: provision.CommitmentAmount.Clone(), 538 } 539 } 540 541 // create party and make it pay liquidity fee. 542 createPartyAndPayLiquidityFee(t, num.NewUint(114101), testLiquidity) 543 544 testLiquidity.liquidityEngine.EXPECT().ProvisionsPerParty().DoAndReturn(func() liquidity.ProvisionsPerParty { 545 return provisionsPerParty 546 }).AnyTimes() 547 548 testLiquidity.liquidityEngine.EXPECT().ResetSLAEpoch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 549 550 // start epoch. 551 lastDistributionStep := time.Now() 552 now := lastDistributionStep.Add(time.Second * 5) 553 testLiquidity.liquidityEngine.EXPECT().ReadyForFeesAllocation(gomock.Any()).Return(true) 554 555 testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne) 556 557 testLiquidity.liquidityEngine.EXPECT().ResetAverageLiquidityScores().AnyTimes() 558 testLiquidity.liquidityEngine.EXPECT().ResetFeeAllocationPeriod(gomock.Any()).AnyTimes() 559 560 testLiquidity.equityShares.EXPECT().AllShares().DoAndReturn(func() map[string]num.Decimal { 561 return weightsPerLP 562 }) 563 564 testLiquidity.liquidityEngine.EXPECT().GetAverageLiquidityScores().DoAndReturn(func() map[string]num.Decimal { 565 return scoresPerLP 566 }) 567 568 // trigger a time tick - this should start allocation fees to LP fee accounts. 569 testLiquidity.marketLiquidity.OnTick(ctx, now) 570 571 for _, provider := range keys { 572 acc, err := testLiquidity.collateralEngine.GetPartyLiquidityFeeAccount( 573 testLiquidity.marketID, 574 provider, 575 testLiquidity.asset, 576 ) 577 assert.NoError(t, err) 578 579 expected := expectedAllocatedFess[provider] 580 assert.True(t, expected.EQ(acc.Balance), "party %s liquidity fee account balance is %s, expected: %s", provider, acc.Balance, expected) 581 } 582 583 zeroPointFive := num.NewDecimalFromFloat(0.5) 584 expectedSLAPenalties := map[string]*liquidity.SlaPenalty{ 585 "lp-1": { 586 Fee: num.NewDecimalFromFloat(0), 587 Bond: zeroPointFive, 588 }, 589 "lp-2": { 590 Fee: num.NewDecimalFromFloat(0.05), 591 Bond: zeroPointFive, 592 }, 593 "lp-3": { 594 Fee: num.NewDecimalFromFloat(0.1), 595 Bond: zeroPointFive, 596 }, 597 "lp-4": { 598 Fee: num.NewDecimalFromFloat(0.2), 599 Bond: zeroPointFive, 600 }, 601 "lp-5": { 602 Fee: num.NewDecimalFromFloat(0.7), 603 Bond: zeroPointFive, 604 }, 605 "lp-6": { 606 Fee: num.NewDecimalFromFloat(1), 607 Bond: zeroPointFive, 608 }, 609 } 610 611 testLiquidity.liquidityEngine.EXPECT().CalculateSLAPenalties(gomock.Any()).DoAndReturn( 612 func(_ time.Time) liquidity.SlaPenalties { 613 return liquidity.SlaPenalties{ 614 PenaltiesPerParty: expectedSLAPenalties, 615 } 616 }, 617 ) 618 619 testLiquidity.liquidityEngine.EXPECT(). 620 LiquidityProvisionByPartyID(gomock.Any()). 621 DoAndReturn(func(party string) *types.LiquidityProvision { 622 return &types.LiquidityProvision{ 623 ID: party, 624 Party: party, 625 CommitmentAmount: commitmentAmount, 626 } 627 }).AnyTimes() 628 629 // end epoch - this should trigger the SLA fees distribution. 630 testLiquidity.marketLiquidity.OnEpochEnd(testLiquidity.ctx, now, types.Epoch{}) 631 632 for _, provider := range keys { 633 generalAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount( 634 provider, 635 testLiquidity.asset, 636 ) 637 assert.NoError(t, err) 638 639 expectedFee := expectedDistributedFess[provider] 640 assert.Truef(t, expectedFee.EQ(generalAcc.Balance), 641 "party %s general account balance is %s, expected: %s", provider, generalAcc.Balance, expectedFee) 642 } 643 644 acc, err := testLiquidity.collateralEngine.GetOrCreateLiquidityFeesBonusDistributionAccount( 645 ctx, 646 testLiquidity.marketID, 647 testLiquidity.asset, 648 ) 649 assert.NoError(t, err) 650 assert.True(t, acc.Balance.EQ(num.UintZero())) 651 652 testLiquidity.equityShares.EXPECT().SetPartyStake(gomock.Any(), gomock.Any()).AnyTimes() 653 testLiquidity.equityShares.EXPECT().AllShares().AnyTimes() 654 testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne) 655 } 656 657 func TestLiquidityProvisionsAmendments(t *testing.T) { 658 testLiquidity := newMarketLiquidity(t) 659 660 ctx := context.Background() 661 662 testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time { 663 return time.Now() 664 }).AnyTimes() 665 666 testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes() 667 testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(map[string]common.AMMPool{}).AnyTimes() 668 669 testLiquidity.liquidityEngine.EXPECT().UpdatePartyCommitment(gomock.Any(), gomock.Any()).DoAndReturn( 670 func(partyID string, amount *num.Uint) (*types.LiquidityProvision, error) { 671 return &types.LiquidityProvision{ 672 Party: partyID, 673 CommitmentAmount: amount.Clone(), 674 }, nil 675 }).AnyTimes() 676 677 // enable asset first. 678 err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{ 679 ID: testLiquidity.asset, 680 Details: &types.AssetDetails{ 681 Name: testLiquidity.asset, 682 Symbol: testLiquidity.asset, 683 Decimals: 0, 684 Source: types.AssetDetailsErc20{ 685 ERC20: &types.ERC20{ 686 ContractAddress: "addrs", 687 ChainID: "1", 688 }, 689 }, 690 }, 691 }) 692 assert.NoError(t, err) 693 694 // create all required accounts for spot market. 695 _, _, err = testLiquidity.collateralEngine.CreateMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset) 696 assert.NoError(t, err) 697 698 testLiquidity.liquidityEngine.EXPECT(). 699 SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 700 AnyTimes() 701 702 testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes() 703 one := num.UintOne() 704 testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes() 705 testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 706 707 provider := "lp-1" 708 commitmentAmount := num.NewUint(10000) 709 710 // create providers general account and deposit funds into it. 711 _, err = testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset) 712 assert.NoError(t, err) 713 714 _, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount) 715 assert.NoError(t, err) 716 717 // submit the provision. 718 provision := &types.LiquidityProvisionSubmission{ 719 MarketID: testLiquidity.marketID, 720 CommitmentAmount: commitmentAmount, 721 Reference: provider, 722 } 723 724 deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider))) 725 err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider, 726 deterministicID, types.MarketStateActive) 727 assert.NoError(t, err) 728 729 bAcc, err := testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset) 730 assert.NoError(t, err) 731 assert.Equal(t, "10000", bAcc.Balance.String()) 732 733 gAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset) 734 assert.NoError(t, err) 735 assert.Equal(t, "0", gAcc.Balance.String()) 736 737 testLiquidity.liquidityEngine.EXPECT(). 738 AmendLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 739 Return(true, nil). 740 AnyTimes() 741 742 testLiquidity.liquidityEngine.EXPECT(). 743 ValidateLiquidityProvisionAmendment(gomock.Any()). 744 Return(nil). 745 AnyTimes() 746 747 testLiquidity.liquidityEngine.EXPECT(). 748 PendingProvisionByPartyID(gomock.Any()). 749 Return(nil). 750 AnyTimes() 751 752 testLiquidity.liquidityEngine.EXPECT(). 753 IsLiquidityProvider(gomock.Any()). 754 Return(true). 755 AnyTimes() 756 757 testLiquidity.liquidityEngine.EXPECT(). 758 LiquidityProvisionByPartyID(gomock.Any()). 759 Return(&types.LiquidityProvision{ 760 ID: provider, 761 Party: provider, 762 CommitmentAmount: commitmentAmount, 763 }). 764 AnyTimes() 765 766 lpa := &types.LiquidityProvisionAmendment{ 767 MarketID: testLiquidity.marketID, 768 CommitmentAmount: num.NewUint(1000), 769 } 770 err = testLiquidity.marketLiquidity.AmendLiquidityProvision(ctx, lpa, provider, 771 deterministicID, types.MarketStateActive) 772 assert.NoError(t, err) 773 774 bAcc, err = testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset) 775 assert.NoError(t, err) 776 assert.Equal(t, "1000", bAcc.Balance.String()) 777 778 gAcc, err = testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset) 779 assert.NoError(t, err) 780 assert.Equal(t, "9000", gAcc.Balance.String()) 781 } 782 783 func TestCancelLiquidityProvisionDuringOpeningAuction(t *testing.T) { 784 testLiquidity := newMarketLiquidity(t) 785 786 ctx := context.Background() 787 788 testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time { 789 return time.Now() 790 }).AnyTimes() 791 792 testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes() 793 794 testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(map[string]common.AMMPool{}).AnyTimes() 795 796 // enable asset first. 797 err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{ 798 ID: testLiquidity.asset, 799 Details: &types.AssetDetails{ 800 Name: testLiquidity.asset, 801 Symbol: testLiquidity.asset, 802 Decimals: 0, 803 Source: types.AssetDetailsErc20{ 804 ERC20: &types.ERC20{ 805 ContractAddress: "addrs", 806 ChainID: "1", 807 }, 808 }, 809 }, 810 }) 811 assert.NoError(t, err) 812 813 // create all required accounts for spot market. 814 _, _, err = testLiquidity.collateralEngine.CreateMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset) 815 assert.NoError(t, err) 816 817 testLiquidity.liquidityEngine.EXPECT(). 818 SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 819 Return(true, nil). 820 AnyTimes() 821 822 testLiquidity.equityShares.EXPECT().SetPartyStake(gomock.Any(), gomock.Any()) 823 testLiquidity.equityShares.EXPECT().AllShares() 824 825 testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes() 826 one := num.UintOne() 827 testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes() 828 testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 829 830 provider := "lp-1" 831 commitmentAmount := num.NewUint(10000) 832 833 // create providers general account and deposit funds into it. 834 _, err = testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset) 835 assert.NoError(t, err) 836 837 _, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount) 838 assert.NoError(t, err) 839 840 // submit the provision. 841 provision := &types.LiquidityProvisionSubmission{ 842 MarketID: testLiquidity.marketID, 843 CommitmentAmount: commitmentAmount, 844 Reference: provider, 845 } 846 847 deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider))) 848 err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider, 849 deterministicID, types.MarketStateActive) 850 assert.NoError(t, err) 851 852 bAcc, err := testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset) 853 assert.NoError(t, err) 854 assert.Equal(t, "10000", bAcc.Balance.String()) 855 856 gAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset) 857 assert.NoError(t, err) 858 assert.Equal(t, "0", gAcc.Balance.String()) 859 860 testLiquidity.liquidityEngine.EXPECT(). 861 AmendLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 862 Return(true, nil). 863 AnyTimes() 864 865 testLiquidity.liquidityEngine.EXPECT(). 866 ValidateLiquidityProvisionAmendment(gomock.Any()). 867 Return(nil). 868 AnyTimes() 869 870 testLiquidity.liquidityEngine.EXPECT(). 871 PendingProvisionByPartyID(gomock.Any()). 872 Return(nil). 873 AnyTimes() 874 875 testLiquidity.liquidityEngine.EXPECT(). 876 IsLiquidityProvider(gomock.Any()). 877 Return(true). 878 AnyTimes() 879 880 testLiquidity.liquidityEngine.EXPECT(). 881 LiquidityProvisionByPartyID(gomock.Any()). 882 Return(&types.LiquidityProvision{ 883 ID: provider, 884 Party: provider, 885 CommitmentAmount: commitmentAmount, 886 }). 887 AnyTimes() 888 889 testLiquidity.equityShares.EXPECT().SetPartyStake(provider, gomock.Any()).Times(1) 890 testLiquidity.equityShares.EXPECT().AllShares().Times(1).Return(nil) 891 err = testLiquidity.marketLiquidity.CancelLiquidityProvision(ctx, provider) 892 assert.NoError(t, err) 893 894 bAcc, err = testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset) 895 assert.NoError(t, err) 896 assert.Equal(t, "0", bAcc.Balance.String()) 897 898 gAcc, err = testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset) 899 assert.NoError(t, err) 900 assert.Equal(t, "10000", gAcc.Balance.String()) 901 }