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  }