code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/games_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 sqlstore_test 17 18 import ( 19 "context" 20 "math/rand" 21 "sort" 22 "testing" 23 "time" 24 25 "code.vegaprotocol.io/vega/datanode/entities" 26 "code.vegaprotocol.io/vega/datanode/sqlstore" 27 "code.vegaprotocol.io/vega/libs/num" 28 "code.vegaprotocol.io/vega/libs/ptr" 29 "code.vegaprotocol.io/vega/libs/slice" 30 "code.vegaprotocol.io/vega/protos/vega" 31 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 32 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 ) 36 37 type gameStores struct { 38 blocks *sqlstore.Blocks 39 assets *sqlstore.Assets 40 accounts *sqlstore.Accounts 41 transfers *sqlstore.Transfers 42 rewards *sqlstore.Rewards 43 parties *sqlstore.Parties 44 games *sqlstore.Games 45 teams *sqlstore.Teams 46 } 47 48 func TestListGames(t *testing.T) { 49 ctx := tempTransaction(t) 50 stores := setupGamesTest(t, ctx) 51 startingBlock := addTestBlockForTime(t, ctx, stores.blocks, time.Now()) 52 gamesData, gameIDs, _, teams, individuals := setupGamesData(ctx, t, stores, startingBlock, 50) 53 src := rand.NewSource(time.Now().UnixNano()) 54 r := rand.New(src) 55 t.Run("Should list all games data if no filter is given", func(t *testing.T) { 56 t.Run("and return all data for the most recent epoch if no epoch is given", func(t *testing.T) { 57 want := filterForEpochs(50, 50, gamesData) 58 t.Run("if no pagination is given", func(t *testing.T) { 59 got, _, err := stores.games.ListGames(ctx, nil, nil, nil, nil, nil, nil, entities.CursorPagination{}) 60 assert.NoError(t, err) 61 assert.Equal(t, want, got) 62 }) 63 64 t.Run("if first page is requested", func(t *testing.T) { 65 first := int32(2) 66 pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true) 67 require.NoError(t, err) 68 got, pageInfo, err := stores.games.ListGames(ctx, nil, nil, nil, nil, nil, nil, pagination) 69 assert.NoError(t, err) 70 want := want[:2] 71 assert.Equal(t, want, got) 72 assert.Equal(t, entities.PageInfo{ 73 HasNextPage: true, 74 HasPreviousPage: false, 75 StartCursor: want[0].Cursor().Encode(), 76 EndCursor: want[1].Cursor().Encode(), 77 }, pageInfo) 78 }) 79 80 t.Run("if first page after cursor is requested", func(t *testing.T) { 81 first := int32(2) 82 after := gamesData[1].Cursor().Encode() 83 pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true) 84 require.NoError(t, err) 85 got, pageInfo, err := stores.games.ListGames(ctx, nil, nil, nil, nil, nil, nil, pagination) 86 assert.NoError(t, err) 87 want := want[2:4] 88 assert.Equal(t, want, got) 89 assert.Equal(t, entities.PageInfo{ 90 HasNextPage: true, 91 HasPreviousPage: true, 92 StartCursor: want[0].Cursor().Encode(), 93 EndCursor: want[1].Cursor().Encode(), 94 }, pageInfo) 95 }) 96 97 t.Run("if last page is requested", func(t *testing.T) { 98 last := int32(2) 99 pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true) 100 require.NoError(t, err) 101 _, _, err = stores.games.ListGames(ctx, nil, nil, nil, nil, nil, nil, pagination) 102 assert.Error(t, err) 103 }) 104 105 t.Run("if last page before cursor is requested", func(t *testing.T) { 106 last := int32(2) 107 before := gamesData[2].Cursor().Encode() 108 pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true) 109 require.NoError(t, err) 110 _, _, err = stores.games.ListGames(ctx, nil, nil, nil, nil, nil, nil, pagination) 111 assert.Error(t, err) 112 }) 113 }) 114 t.Run("and return data from the start to most recent epoch if no end epoch is given", func(t *testing.T) { 115 t.Run("when start is less than 30 epochs before most recent", func(t *testing.T) { 116 epochFrom := uint64(1) 117 epochTo := uint64(20) 118 got, _, err := stores.games.ListGames(ctx, nil, nil, ptr.From(epochFrom), ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 119 require.NoError(t, err) 120 want := filterForEpochs(1, 20, gamesData) 121 require.Equal(t, len(want), len(got)) 122 assert.Equal(t, want, got) 123 }) 124 t.Run("when start is more than 30 epochs before most recent", func(t *testing.T) { 125 want := filterForEpochs(1, 30, gamesData) 126 epochFrom := uint64(1) 127 got, _, err := stores.games.ListGames(ctx, nil, nil, ptr.From(epochFrom), nil, nil, nil, entities.CursorPagination{}) 128 require.NoError(t, err) 129 require.Equal(t, len(want), len(got)) 130 assert.Equal(t, want, got) 131 }) 132 }) 133 t.Run("and return all data from 30 previous epochs to given end epoch", func(t *testing.T) { 134 t.Run("if no start epoch given", func(t *testing.T) { 135 epochTo := uint64(40) 136 got, _, err := stores.games.ListGames(ctx, nil, nil, nil, ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 137 require.NoError(t, err) 138 want := filterForEpochs(11, 40, gamesData) 139 require.Equal(t, len(want), len(got)) 140 assert.Equal(t, want, got) 141 }) 142 t.Run("if start is more than 30 epochs before end", func(t *testing.T) { 143 epochFrom := uint64(1) 144 epochTo := uint64(40) 145 got, _, err := stores.games.ListGames(ctx, nil, nil, ptr.From(epochFrom), ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 146 require.NoError(t, err) 147 want := filterForEpochs(11, 40, gamesData) 148 require.Equal(t, len(want), len(got)) 149 assert.Equal(t, want, got) 150 }) 151 }) 152 }) 153 t.Run("Should list a game's stats if gameID is provided", func(t *testing.T) { 154 t.Run("and return data from the most recent epoch if no epoch is given", func(t *testing.T) { 155 i := r.Intn(len(gameIDs)) 156 gameID := gameIDs[i] 157 want := filterForGameID(filterForEpochs(50, 50, gamesData), gameID) 158 got, _, err := stores.games.ListGames(ctx, ptr.From(gameID), nil, nil, nil, nil, nil, entities.CursorPagination{}) 159 require.NoError(t, err) 160 require.Equal(t, len(want), len(got)) 161 assert.Equal(t, want, got) 162 }) 163 t.Run("and return data for the 30 epochs up to the given end epoch", func(t *testing.T) { 164 i := r.Intn(len(gameIDs)) 165 gameID := gameIDs[i] 166 want := filterForGameID(filterForEpochs(11, 40, gamesData), gameID) 167 epochTo := uint64(40) 168 got, _, err := stores.games.ListGames(ctx, ptr.From(gameID), nil, nil, ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 169 require.NoError(t, err) 170 require.Equal(t, len(want), len(got)) 171 assert.Equal(t, want, got) 172 }) 173 t.Run("and return data between the given start and end epochs", func(t *testing.T) { 174 i := r.Intn(len(gameIDs)) 175 gameID := gameIDs[i] 176 want := filterForGameID(filterForEpochs(21, 40, gamesData), gameID) 177 epochFrom := uint64(21) 178 epochTo := uint64(40) 179 got, _, err := stores.games.ListGames(ctx, ptr.From(gameID), nil, ptr.From(epochFrom), ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 180 require.NoError(t, err) 181 require.Equal(t, len(want), len(got)) 182 assert.Equal(t, want, got) 183 }) 184 }) 185 t.Run("Should list games for a specific entity type if specified", func(t *testing.T) { 186 t.Run("and return data for the most recent epoch if no epoch is given", func(t *testing.T) { 187 t.Run("when entity scope is teams", func(t *testing.T) { 188 entityScope := vega.EntityScope_ENTITY_SCOPE_TEAMS 189 want := filterForEntityScope(filterForEpochs(50, 50, gamesData), entityScope) 190 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(entityScope), nil, nil, nil, nil, entities.CursorPagination{}) 191 require.NoError(t, err) 192 require.Equal(t, len(want), len(got)) 193 assert.Equal(t, want, got) 194 }) 195 t.Run("when entity scope is individuals", func(t *testing.T) { 196 entityScope := vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS 197 want := filterForEntityScope(filterForEpochs(50, 50, gamesData), entityScope) 198 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(entityScope), nil, nil, nil, nil, entities.CursorPagination{}) 199 require.NoError(t, err) 200 require.Equal(t, len(want), len(got)) 201 assert.Equal(t, want, got) 202 }) 203 }) 204 t.Run("and return data for the 30 epochs up to the given end epoch", func(t *testing.T) { 205 t.Run("when entity scope is teams", func(t *testing.T) { 206 entityScope := vega.EntityScope_ENTITY_SCOPE_TEAMS 207 want := filterForEntityScope(filterForEpochs(11, 40, gamesData), entityScope) 208 epochTo := uint64(40) 209 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(entityScope), nil, ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 210 require.NoError(t, err) 211 require.Equal(t, len(want), len(got)) 212 for i, w := range want { 213 for j, e := range w.Entities { 214 wt := e.(*entities.TeamGameEntity) 215 gt := got[i].Entities[j].(*entities.TeamGameEntity) 216 assert.Equalf(t, wt.Team.TeamID, gt.Team.TeamID, "TeamID mismatch, game index: %d, entity index: %d", i, j) 217 for k, m := range wt.Team.MembersParticipating { 218 assert.Equalf(t, m.Individual, gt.Team.MembersParticipating[k].Individual, "Individual mismatch, game index: %d, entity index: %d, member index: %d", i, j, k) 219 assert.Equal(t, m.Rank, gt.Team.MembersParticipating[k].Rank, "Rank mismatch, game index: %d, entity index: %d, member index: %d", i, j, k) 220 } 221 assert.Equal(t, wt.Rank, gt.Rank, "Rank mismatch, game index: %d, entity index: %d", i, j) 222 } 223 } 224 assert.Equal(t, want, got) 225 }) 226 t.Run("when entity scope is individuals", func(t *testing.T) { 227 entityScope := vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS 228 want := filterForEntityScope(filterForEpochs(11, 40, gamesData), entityScope) 229 epochTo := uint64(40) 230 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(entityScope), nil, ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 231 require.NoError(t, err) 232 require.Equal(t, len(want), len(got)) 233 assert.Equal(t, want, got) 234 }) 235 }) 236 t.Run("and return data between the given start and end epochs", func(t *testing.T) { 237 t.Run("when entity scope is teams", func(t *testing.T) { 238 entityScope := vega.EntityScope_ENTITY_SCOPE_TEAMS 239 want := filterForEntityScope(filterForEpochs(21, 40, gamesData), entityScope) 240 epochFrom := uint64(21) 241 epochTo := uint64(40) 242 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(entityScope), ptr.From(epochFrom), ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 243 require.NoError(t, err) 244 require.Equal(t, len(want), len(got)) 245 assert.Equal(t, want, got) 246 }) 247 t.Run("when entity scope is individuals", func(t *testing.T) { 248 entityScope := vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS 249 want := filterForEntityScope(filterForEpochs(21, 40, gamesData), entityScope) 250 epochFrom := uint64(21) 251 epochTo := uint64(40) 252 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(entityScope), ptr.From(epochFrom), ptr.From(epochTo), nil, nil, entities.CursorPagination{}) 253 require.NoError(t, err) 254 require.Equal(t, len(want), len(got)) 255 assert.Equal(t, want, got) 256 }) 257 }) 258 }) 259 t.Run("Should list game stats for a team if entity scope is not set and team ID is provided", func(t *testing.T) { 260 t.Run("and return data from the most recent epoch if no epoch is given", func(t *testing.T) { 261 // Randomly choose a team 262 teamID := pickRandomTeam(r, teams) 263 want := filterForTeamID(filterForEpochs(50, 50, gamesData), teamID.String()) 264 got, _, err := stores.games.ListGames(ctx, nil, nil, nil, nil, ptr.From(teamID), nil, entities.CursorPagination{}) 265 require.NoError(t, err) 266 require.Equal(t, len(want), len(got)) 267 assert.Equal(t, want, got) 268 }) 269 }) 270 t.Run("Should list games stats for an individual", func(t *testing.T) { 271 t.Run("And the entity scope is not set and individual ID is provided", func(t *testing.T) { 272 i := r.Intn(100) 273 var partyID entities.PartyID 274 275 if i%2 == 0 { 276 // choose a random team member 277 teamID := pickRandomTeam(r, teams) 278 members := teams[teamID.String()] 279 j := r.Intn(len(members)) 280 partyID = members[j].ID 281 } else { 282 // choose a random individual 283 j := r.Intn(len(individuals)) 284 partyID = individuals[j].ID 285 } 286 want := filterForPartyID(filterForEpochs(50, 50, gamesData), partyID.String()) 287 got, _, err := stores.games.ListGames(ctx, nil, nil, nil, nil, nil, ptr.From(partyID), entities.CursorPagination{}) 288 require.NoError(t, err) 289 require.Equal(t, len(want), len(got)) 290 assert.Equal(t, want, got) 291 }) 292 t.Run("And the entity scope is teams and individual ID is provided", func(t *testing.T) { 293 teamID := pickRandomTeam(r, teams) 294 members := teams[teamID.String()] 295 j := r.Intn(len(members)) 296 partyID := members[j].ID 297 298 want := filterForPartyID(filterForEpochs(50, 50, gamesData), partyID.String()) 299 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(vega.EntityScope_ENTITY_SCOPE_TEAMS), nil, nil, nil, ptr.From(partyID), entities.CursorPagination{}) 300 require.NoError(t, err) 301 require.Equal(t, len(want), len(got)) 302 assert.Equal(t, want, got) 303 }) 304 t.Run("And the entity scope is individuals and individual ID is provided", func(t *testing.T) { 305 // choose a random individual 306 j := r.Intn(len(individuals)) 307 partyID := individuals[j].ID 308 309 want := filterForPartyID(filterForEpochs(50, 50, gamesData), partyID.String()) 310 got, _, err := stores.games.ListGames(ctx, nil, ptr.From(vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS), nil, nil, nil, ptr.From(partyID), entities.CursorPagination{}) 311 require.NoError(t, err) 312 require.Equal(t, len(want), len(got)) 313 assert.Equal(t, want, got) 314 }) 315 }) 316 } 317 318 func setupGamesTest(t *testing.T, ctx context.Context) gameStores { 319 t.Helper() 320 return gameStores{ 321 blocks: sqlstore.NewBlocks(connectionSource), 322 assets: sqlstore.NewAssets(connectionSource), 323 accounts: sqlstore.NewAccounts(connectionSource), 324 transfers: sqlstore.NewTransfers(connectionSource), 325 rewards: sqlstore.NewRewards(ctx, connectionSource), 326 parties: sqlstore.NewParties(connectionSource), 327 games: sqlstore.NewGames(connectionSource), 328 teams: sqlstore.NewTeams(connectionSource), 329 } 330 } 331 332 type gameDataKey struct { 333 ID string 334 Epoch int64 335 } 336 337 func setupResultsStore(t *testing.T, gameIDs []string, epochCount int64) map[gameDataKey]entities.Game { 338 t.Helper() 339 340 store := make(map[gameDataKey]entities.Game) 341 for _, id := range gameIDs { 342 for epoch := int64(1); epoch <= epochCount; epoch++ { 343 key := gameDataKey{ 344 ID: id, 345 Epoch: epoch, 346 } 347 store[key] = entities.Game{ 348 ID: entities.GameID(id), 349 Epoch: uint64(epoch), 350 } 351 } 352 } 353 354 return store 355 } 356 357 func setupGamesData(ctx context.Context, t *testing.T, stores gameStores, block entities.Block, epochCount int64) ( 358 []entities.Game, []string, map[gameDataKey][]entities.Reward, map[string][]entities.Party, []entities.Party, 359 ) { 360 t.Helper() 361 362 gameCount := 5 363 teamCount := 3 364 individualCount := 5 365 366 gameIDs := getGameIDs(t, gameCount) 367 gameEntities := setupResultsStore(t, gameIDs, epochCount) 368 teams := getTeams(t, ctx, stores, block, teamCount) 369 individuals := getIndividuals(t, ctx, stores, block, individualCount) 370 gameAssets := make(map[string]*entities.Asset) 371 gameEntityScopes := make(map[string]vega.EntityScope) 372 373 i := 0 374 for _, gameID := range gameIDs { 375 gID := entities.GameID(gameID) 376 asset := CreateAsset(t, ctx, stores.assets, block) 377 fromAccount := CreateAccount(t, ctx, stores.accounts, block, AccountForAsset(asset)) 378 toAccount := CreateAccount(t, ctx, stores.accounts, block, AccountWithType(vega.AccountType_ACCOUNT_TYPE_GLOBAL_REWARD), AccountForAsset(asset)) 379 // create the recurring transfers that are games 380 var recurringTransfer eventspb.RecurringTransfer 381 if i%2 == 0 { 382 recurringTransfer = eventspb.RecurringTransfer{ 383 StartEpoch: 1, 384 Factor: "0.1", 385 DispatchStrategy: &vega.DispatchStrategy{ 386 AssetForMetric: asset.ID.String(), 387 Metric: vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, 388 EntityScope: vega.EntityScope_ENTITY_SCOPE_TEAMS, 389 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA, 390 }, 391 } 392 gameEntityScopes[gameID] = vega.EntityScope_ENTITY_SCOPE_TEAMS 393 } else { 394 recurringTransfer = eventspb.RecurringTransfer{ 395 StartEpoch: 1, 396 Factor: "0.1", 397 DispatchStrategy: &vega.DispatchStrategy{ 398 AssetForMetric: asset.ID.String(), 399 Metric: vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, 400 EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS, 401 IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_ALL, 402 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA, 403 }, 404 } 405 gameEntityScopes[gameID] = vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS 406 } 407 transfer := NewTransfer(t, ctx, stores.accounts, block, 408 TransferWithAsset(asset), 409 TransferFromToAccounts(fromAccount, toAccount), 410 TransferAsRecurring(&recurringTransfer), 411 TransferWithGameID(ptr.From(gID.String())), 412 ) 413 414 err := stores.transfers.Upsert(ctx, transfer) 415 require.NoError(t, err) 416 gameAssets[gameID] = asset 417 i++ 418 } 419 420 rewards := make(map[gameDataKey][]entities.Reward) 421 src := rand.NewSource(time.Now().UnixNano()) 422 r := rand.New(src) 423 424 teamTotalRewards := make(map[gameDataKey]map[string]*num.Uint) 425 teamMemberTotalRewards := make(map[gameDataKey]map[string]map[string]*num.Uint) 426 individualTotalRewards := make(map[gameDataKey]map[string]*num.Uint) 427 428 for epoch := int64(1); epoch <= epochCount; epoch++ { 429 block = addTestBlockForTime(t, ctx, stores.blocks, block.VegaTime.Add(time.Minute)) 430 seqNum := uint64(1) 431 for _, gameID := range gameIDs { 432 // create the rewards for the games 433 // we want to create the rewards for each participant in the game 434 participants := uint64(0) 435 market := entities.MarketID(GenerateID()) 436 teamEntities := make([]entities.GameEntity, 0) 437 individualEntities := make([]entities.GameEntity, 0) 438 gID := entities.GameID(gameID) 439 asset := gameAssets[gameID] 440 gk := gameDataKey{ 441 ID: gameID, 442 Epoch: epoch, 443 } 444 pk := gameDataKey{ 445 ID: gameID, 446 Epoch: epoch - 1, 447 } 448 if gameEntityScopes[gameID] == vega.EntityScope_ENTITY_SCOPE_TEAMS { 449 if teamTotalRewards[gk] == nil { 450 teamTotalRewards[gk] = make(map[string]*num.Uint) 451 } 452 if teamMemberTotalRewards[gk] == nil { 453 teamMemberTotalRewards[gk] = make(map[string]map[string]*num.Uint) 454 } 455 for team, members := range teams { 456 if teamMemberTotalRewards[gk][team] == nil { 457 teamMemberTotalRewards[gk][team] = make(map[string]*num.Uint) 458 // carry forward the previous totals 459 if teamMemberTotalRewards[pk] != nil && teamMemberTotalRewards[pk][team] != nil { 460 for k, v := range teamMemberTotalRewards[pk][team] { 461 teamMemberTotalRewards[gk][team][k] = v.Clone() 462 } 463 } 464 } 465 teamRewards := num.NewUint(0) 466 teamVolume := num.DecimalZero() 467 memberEntities := make([]*entities.IndividualGameEntity, 0) 468 for _, member := range members { 469 amount := num.DecimalFromInt64(r.Int63n(1000)) 470 reward := addTestReward(t, ctx, stores.rewards, member, *asset, market, epoch, "", block.VegaTime, block, seqNum, amount, generateTxHash(), &gID) 471 reward.TeamID = ptr.From(entities.TeamID(team)) 472 rewards[gk] = append(rewards[gk], reward) 473 rewardEarned, _ := num.UintFromDecimal(amount) 474 individualEntity := entities.IndividualGameEntity{ 475 Individual: member.ID.String(), 476 Rank: 0, 477 Volume: num.DecimalZero(), 478 RewardMetric: vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, 479 RewardEarned: rewardEarned, 480 RewardEarnedQuantum: rewardEarned, 481 } 482 teamRewards = teamRewards.Add(teamRewards, individualEntity.RewardEarned) 483 teamVolume = teamVolume.Add(individualEntity.Volume) 484 if _, ok := teamMemberTotalRewards[pk][team][member.ID.String()]; !ok { 485 teamMemberTotalRewards[gk][team][member.ID.String()] = num.NewUint(0) 486 } else { 487 // carry forward the previous totals 488 teamMemberTotalRewards[gk][team][member.ID.String()] = teamMemberTotalRewards[pk][team][member.ID.String()].Clone() 489 } 490 teamMemberTotalRewards[gk][team][member.ID.String()] = teamMemberTotalRewards[gk][team][member.ID.String()]. 491 Add(teamMemberTotalRewards[gk][team][member.ID.String()], individualEntity.RewardEarned) 492 individualEntity.TotalRewardsEarned = teamMemberTotalRewards[gk][team][member.ID.String()] 493 individualEntity.TotalRewardsEarnedQuantum = teamMemberTotalRewards[gk][team][member.ID.String()] 494 memberEntities = append(memberEntities, &individualEntity) 495 participants++ 496 seqNum++ 497 } 498 // Rank the individual members of the team participating in the game 499 sort.Slice(memberEntities, func(i, j int) bool { 500 return memberEntities[i].TotalRewardsEarned.GT(memberEntities[j].TotalRewardsEarned) 501 }) 502 // now assign the individual member ranks 503 for i := range memberEntities { 504 memberEntities[i].Rank = uint64(i + 1) 505 } 506 teamEntity := entities.TeamGameEntity{ 507 Team: entities.TeamGameParticipation{ 508 TeamID: entities.TeamID(team), 509 MembersParticipating: memberEntities, 510 }, 511 Rank: 0, 512 Volume: teamVolume, 513 RewardMetric: vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, 514 RewardEarned: teamRewards, 515 RewardEarnedQuantum: teamRewards, 516 } 517 if teamTotalRewards[gk][team] == nil { 518 if teamTotalRewards[pk] == nil || teamTotalRewards[pk][team] == nil { 519 teamTotalRewards[gk][team] = num.NewUint(0) 520 } else { 521 teamTotalRewards[gk][team] = teamTotalRewards[pk][team].Clone() 522 } 523 } 524 teamTotalRewards[gk][team] = teamTotalRewards[gk][team].Add(teamTotalRewards[gk][team], teamRewards) 525 teamEntity.TotalRewardsEarned = teamTotalRewards[gk][team] 526 teamEntity.TotalRewardsEarnedQuantum = teamTotalRewards[gk][team] 527 teamEntities = append(teamEntities, &teamEntity) 528 } 529 // now let's order the team totals and set the ranks for each team 530 teamRanking := rankEntity(teamTotalRewards[gk]) 531 for _, ge := range teamEntities { 532 te := ge.(*entities.TeamGameEntity) 533 memberRankings := rankEntity(teamMemberTotalRewards[gk][te.Team.TeamID.String()]) 534 te.Rank = teamRanking[te.Team.TeamID.String()] 535 for _, m := range te.Team.MembersParticipating { 536 m.Rank = memberRankings[m.Individual] 537 } 538 // now that the team members have been ranked, we need to order the team members by rank 539 sort.Slice(te.Team.MembersParticipating, func(i, j int) bool { 540 return te.Team.MembersParticipating[i].Rank < te.Team.MembersParticipating[j].Rank || (te.Team.MembersParticipating[i].Rank == te.Team.MembersParticipating[j].Rank && 541 te.Team.MembersParticipating[i].Individual < te.Team.MembersParticipating[j].Individual) 542 }) 543 } 544 545 // now that we have the ranks for the teams ranked, we need to order the team entities by rank 546 sort.Slice(teamEntities, func(i, j int) bool { 547 return teamEntities[i].(*entities.TeamGameEntity).Rank < teamEntities[j].(*entities.TeamGameEntity).Rank || (teamEntities[i].(*entities.TeamGameEntity).Rank == teamEntities[j].(*entities.TeamGameEntity).Rank && 548 teamEntities[i].(*entities.TeamGameEntity).Team.TeamID.String() < teamEntities[j].(*entities.TeamGameEntity).Team.TeamID.String()) 549 }) 550 551 gameEntity := gameEntities[gk] 552 gameEntity.Participants = participants 553 gameEntity.Entities = teamEntities 554 gameEntity.RewardAssetID = asset.ID 555 556 gameEntities[gk] = gameEntity 557 } else { 558 if individualTotalRewards[gk] == nil { 559 individualTotalRewards[gk] = make(map[string]*num.Uint) 560 if individualTotalRewards[pk] != nil { 561 // carry forward the previous totals for the individuals 562 for k, v := range individualTotalRewards[pk] { 563 individualTotalRewards[gk][k] = v.Clone() 564 } 565 } 566 } 567 for i, individual := range individuals { 568 amount := num.DecimalFromInt64(r.Int63n(1000)) 569 reward := addTestReward(t, ctx, stores.rewards, individual, *asset, market, epoch, "", block.VegaTime, block, seqNum, amount, generateTxHash(), &gID) 570 rewards[gk] = append(rewards[gk], reward) 571 rewardEarned, _ := num.UintFromDecimal(amount) 572 individualEntity := entities.IndividualGameEntity{ 573 Individual: individual.ID.String(), 574 Rank: uint64(i + 1), 575 Volume: num.DecimalZero(), 576 RewardMetric: vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, 577 RewardEarned: rewardEarned, 578 RewardEarnedQuantum: rewardEarned, 579 } 580 individualEntities = append(individualEntities, &individualEntity) 581 seqNum++ 582 participants++ 583 if _, ok := individualTotalRewards[gk][individual.ID.String()]; !ok { 584 individualTotalRewards[gk][individual.ID.String()] = num.NewUint(0) 585 } else { 586 // carry forward the previous totals 587 individualTotalRewards[gk][individual.ID.String()] = individualTotalRewards[pk][individual.ID.String()].Clone() 588 } 589 individualTotalRewards[gk][individual.ID.String()]. 590 Add(individualTotalRewards[gk][individual.ID.String()], individualEntity.RewardEarned) 591 individualEntity.TotalRewardsEarned = individualTotalRewards[gk][individual.ID.String()] 592 individualEntity.TotalRewardsEarnedQuantum = individualTotalRewards[gk][individual.ID.String()] 593 } 594 individualRanking := rankEntity(individualTotalRewards[gk]) 595 for _, ge := range individualEntities { 596 ie := ge.(*entities.IndividualGameEntity) 597 ie.Rank = individualRanking[ie.Individual] 598 } 599 sort.Slice(individualEntities, func(i, j int) bool { 600 return individualEntities[i].(*entities.IndividualGameEntity).Rank < individualEntities[j].(*entities.IndividualGameEntity).Rank || (individualEntities[i].(*entities.IndividualGameEntity).Rank == individualEntities[j].(*entities.IndividualGameEntity).Rank && 601 individualEntities[i].(*entities.IndividualGameEntity).Individual < individualEntities[j].(*entities.IndividualGameEntity).Individual) 602 }) 603 604 gameEntity := gameEntities[gk] 605 gameEntity.Participants = participants 606 gameEntity.Entities = individualEntities 607 gameEntity.RewardAssetID = asset.ID 608 609 gameEntities[gk] = gameEntity 610 } 611 } 612 } 613 614 results := make([]entities.Game, 0, len(gameEntities)) 615 for _, game := range gameEntities { 616 results = append(results, game) 617 } 618 619 // IMPORTANT!!!! We MUST refresh the materialized views or the tests will fail because there will be NO DATA!!! 620 _, err := connectionSource.Exec(ctx, "REFRESH MATERIALIZED VIEW game_stats") 621 require.NoError(t, err) 622 _, err = connectionSource.Exec(ctx, "REFRESH MATERIALIZED VIEW game_stats_current") 623 require.NoError(t, err) 624 625 return orderResults(results), gameIDs, rewards, teams, individuals 626 } 627 628 func orderResults(results []entities.Game) []entities.Game { 629 sort.Slice(results, func(i, j int) bool { 630 return results[i].Epoch > results[j].Epoch || 631 (results[i].Epoch == results[j].Epoch && results[i].ID.String() < results[j].ID.String()) 632 }) 633 return results 634 } 635 636 func filterForEpochs(start, end int64, gamesData []entities.Game) []entities.Game { 637 validEpochs := make([]int64, end-start+1) 638 for i := range validEpochs { 639 validEpochs[i] = start + int64(i) 640 } 641 filtered := make([]entities.Game, 0) 642 for _, game := range gamesData { 643 if slice.Contains(validEpochs, int64(game.Epoch)) { 644 filtered = append(filtered, game) 645 } 646 } 647 // ensure we are correctly ordered 648 return orderResults(filtered) 649 } 650 651 func filterForGameID(gamesData []entities.Game, gameID string) []entities.Game { 652 filtered := make([]entities.Game, 0) 653 for _, game := range gamesData { 654 if game.ID.String() == gameID { 655 filtered = append(filtered, game) 656 } 657 } 658 return orderResults(filtered) 659 } 660 661 func filterForTeamID(gamesData []entities.Game, teamID string) []entities.Game { 662 filtered := make([]entities.Game, 0) 663 for _, game := range gamesData { 664 for _, entity := range game.Entities { 665 if teamEntity, ok := entity.(*entities.TeamGameEntity); ok { 666 if teamEntity.Team.TeamID.String() == teamID { 667 filtered = append(filtered, game) 668 break 669 } 670 } 671 } 672 } 673 674 return filtered 675 } 676 677 func filterForPartyID(gamesData []entities.Game, partyID string) []entities.Game { 678 filtered := make([]entities.Game, 0) 679 for _, game := range gamesData { 680 for _, entity := range game.Entities { 681 switch e := entity.(type) { 682 case *entities.TeamGameEntity: 683 for _, member := range e.Team.MembersParticipating { 684 if member.Individual == partyID { 685 filtered = append(filtered, game) 686 break 687 } 688 } 689 case *entities.IndividualGameEntity: 690 if e.Individual == partyID { 691 filtered = append(filtered, game) 692 break 693 } 694 } 695 } 696 } 697 return orderResults(filtered) 698 } 699 700 func filterForEntityScope(gamesData []entities.Game, entityScope vega.EntityScope) []entities.Game { 701 filtered := make([]entities.Game, 0) 702 for _, game := range gamesData { 703 if len(game.Entities) == 0 { 704 continue 705 } 706 switch game.Entities[0].(type) { 707 case *entities.TeamGameEntity: 708 if entityScope == vega.EntityScope_ENTITY_SCOPE_TEAMS { 709 filtered = append(filtered, game) 710 } 711 case *entities.IndividualGameEntity: 712 if entityScope == vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS { 713 filtered = append(filtered, game) 714 } 715 } 716 } 717 return orderResults(filtered) 718 } 719 720 func getGameIDs(t *testing.T, count int) []string { 721 t.Helper() 722 ids := make([]string, count) 723 for i := 0; i < count; i++ { 724 ids[i] = GenerateID() 725 } 726 return ids 727 } 728 729 func getTeams(t *testing.T, ctx context.Context, stores gameStores, block entities.Block, count int) map[string][]entities.Party { 730 t.Helper() 731 teams := make(map[string][]entities.Party) 732 for i := 0; i < count; i++ { 733 teamID := entities.TeamID(GenerateID()) 734 referrer := entities.PartyID(GenerateID()) 735 team := entities.Team{ 736 ID: teamID, 737 Referrer: referrer, 738 Name: "", 739 TeamURL: nil, 740 AvatarURL: nil, 741 Closed: false, 742 CreatedAt: block.VegaTime, 743 CreatedAtEpoch: 0, 744 VegaTime: block.VegaTime, 745 } 746 err := stores.teams.AddTeam(ctx, &team) 747 require.NoError(t, err) 748 749 members := make([]entities.Party, count) 750 for j := 0; j < count; j++ { 751 members[j] = addTestParty(t, ctx, stores.parties, block) 752 referee := entities.TeamMember{ 753 TeamID: teamID, 754 PartyID: members[j].ID, 755 JoinedAt: block.VegaTime, 756 JoinedAtEpoch: 0, 757 VegaTime: block.VegaTime, 758 } 759 err := stores.teams.RefereeJoinedTeam(ctx, &referee) 760 require.NoError(t, err) 761 } 762 teams[teamID.String()] = members 763 } 764 return teams 765 } 766 767 func getIndividuals(t *testing.T, ctx context.Context, stores gameStores, block entities.Block, count int) []entities.Party { 768 t.Helper() 769 individuals := make([]entities.Party, count) 770 for i := 0; i < count; i++ { 771 individuals[i] = addTestParty(t, ctx, stores.parties, block) 772 } 773 return individuals 774 } 775 776 func rankEntity(entities map[string]*num.Uint) map[string]uint64 { 777 type entityRank struct { 778 ID string 779 Total *num.Uint 780 } 781 entityRanks := make([]entityRank, 0, len(entities)) 782 for k, v := range entities { 783 entityRanks = append(entityRanks, entityRank{ 784 ID: k, 785 Total: v, 786 }) 787 } 788 sort.Slice(entityRanks, func(i, j int) bool { 789 return entityRanks[i].Total.GT(entityRanks[j].Total) || (entityRanks[i].Total.EQ(entityRanks[j].Total) && entityRanks[i].ID < entityRanks[j].ID) 790 }) 791 // now that we have the totals ordered, we can assign ranks 792 ranks := make(map[string]uint64) 793 for i, e := range entityRanks { 794 if i > 0 && e.Total.EQ(entityRanks[i-1].Total) { 795 // if the totals are the same, they should have the same rank 796 ranks[e.ID] = ranks[entityRanks[i-1].ID] 797 continue 798 } 799 ranks[e.ID] = uint64(i + 1) 800 } 801 return ranks 802 } 803 804 func pickRandomTeam(r *rand.Rand, teams map[string][]entities.Party) entities.TeamID { 805 i := r.Intn(len(teams)) 806 j := 0 807 for k := range teams { 808 if i == j { 809 return entities.TeamID(k) 810 } 811 j++ 812 } 813 return "" 814 }