code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/amm_pool_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  
    30  	"github.com/georgysavva/scany/pgxscan"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestAMMPool_Upsert(t *testing.T) {
    36  	ctx := tempTransaction(t)
    37  
    38  	bs := sqlstore.NewBlocks(connectionSource)
    39  	ps := sqlstore.NewAMMPools(connectionSource)
    40  	block := addTestBlock(t, ctx, bs)
    41  
    42  	partyID := entities.PartyID(GenerateID())
    43  	marketID := entities.MarketID(GenerateID())
    44  	poolID := entities.AMMPoolID(GenerateID())
    45  	ammPartyID := entities.PartyID(GenerateID())
    46  
    47  	t.Run("Upsert statuses", func(t *testing.T) {
    48  		upsertTests := []struct {
    49  			Status entities.AMMStatus
    50  			Reason entities.AMMStatusReason
    51  		}{
    52  			{entities.AMMStatusActive, entities.AMMStatusReasonUnspecified},
    53  			{entities.AMMStatusStopped, entities.AMMStatusReasonUnspecified},
    54  			{entities.AMMStatusCancelled, entities.AMMStatusReasonUnspecified},
    55  			{entities.AMMStatusRejected, entities.AMMStatusReasonCancelledByParty},
    56  			{entities.AMMStatusRejected, entities.AMMStatusReasonCannotRebase},
    57  			{entities.AMMStatusRejected, entities.AMMStatusReasonMarketClosed},
    58  			{entities.AMMStatusRejected, entities.AMMStatusReasonCannotFillCommitment},
    59  			{entities.AMMStatusRejected, entities.AMMStatusReasonCommitmentTooLow},
    60  			{entities.AMMStatusRejected, entities.AMMStatusReasonPartyAlreadyOwnsAPool},
    61  			{entities.AMMStatusRejected, entities.AMMStatusReasonPartyClosedOut},
    62  		}
    63  
    64  		upsertTime := block.VegaTime
    65  		for i, test := range upsertTests {
    66  			upsertTime = upsertTime.Add(time.Duration(i) * time.Minute)
    67  			pool := entities.AMMPool{
    68  				PartyID:                        partyID,
    69  				MarketID:                       marketID,
    70  				ID:                             poolID,
    71  				AmmPartyID:                     ammPartyID,
    72  				Commitment:                     num.DecimalFromInt64(100),
    73  				Status:                         test.Status,
    74  				StatusReason:                   test.Reason,
    75  				ParametersBase:                 num.DecimalFromInt64(100),
    76  				ParametersLowerBound:           ptr.From(num.DecimalFromInt64(100)),
    77  				ParametersUpperBound:           ptr.From(num.DecimalFromInt64(100)),
    78  				ParametersLeverageAtLowerBound: ptr.From(num.DecimalFromInt64(100)),
    79  				ParametersLeverageAtUpperBound: ptr.From(num.DecimalFromInt64(100)),
    80  				CreatedAt:                      block.VegaTime,
    81  				LastUpdated:                    upsertTime,
    82  				LowerVirtualLiquidity:          num.DecimalOne(),
    83  				UpperVirtualLiquidity:          num.DecimalOne(),
    84  				LowerTheoreticalPosition:       num.DecimalOne(),
    85  				UpperTheoreticalPosition:       num.DecimalOne(),
    86  				MinimumPriceChangeTrigger:      num.DecimalOne(),
    87  				DataSourceID:                   entities.SpecID(""),
    88  			}
    89  			require.NoError(t, ps.Upsert(ctx, pool))
    90  			var upserted entities.AMMPool
    91  			require.NoError(t, pgxscan.Get(
    92  				ctx,
    93  				connectionSource,
    94  				&upserted,
    95  				`SELECT * FROM amms WHERE party_id = $1 AND market_id = $2 AND id = $3 AND amm_party_id = $4`,
    96  				partyID, marketID, poolID, ammPartyID))
    97  			assert.Equal(t, pool, upserted)
    98  		}
    99  	})
   100  
   101  	t.Run("Upsert with different commitments and bounds", func(t *testing.T) {
   102  		amounts := []num.Decimal{
   103  			num.DecimalFromInt64(100),
   104  			num.DecimalFromInt64(200),
   105  			num.DecimalFromInt64(300),
   106  		}
   107  		upsertTime := block.VegaTime
   108  		for i, amount := range amounts {
   109  			upsertTime = upsertTime.Add(time.Duration(i) * time.Minute)
   110  			pool := entities.AMMPool{
   111  				PartyID:                        partyID,
   112  				MarketID:                       marketID,
   113  				ID:                             poolID,
   114  				AmmPartyID:                     ammPartyID,
   115  				Commitment:                     amount,
   116  				Status:                         entities.AMMStatusActive,
   117  				StatusReason:                   entities.AMMStatusReasonUnspecified,
   118  				ParametersBase:                 amount,
   119  				ParametersLowerBound:           ptr.From(amount),
   120  				ParametersUpperBound:           ptr.From(amount),
   121  				ParametersLeverageAtLowerBound: ptr.From(amount),
   122  				ParametersLeverageAtUpperBound: ptr.From(amount),
   123  				CreatedAt:                      block.VegaTime,
   124  				LastUpdated:                    upsertTime,
   125  				LowerVirtualLiquidity:          num.DecimalOne(),
   126  				UpperVirtualLiquidity:          num.DecimalOne(),
   127  				LowerTheoreticalPosition:       num.DecimalOne(),
   128  				UpperTheoreticalPosition:       num.DecimalOne(),
   129  				MinimumPriceChangeTrigger:      num.DecimalOne(),
   130  			}
   131  			require.NoError(t, ps.Upsert(ctx, pool))
   132  			var upserted entities.AMMPool
   133  			require.NoError(t, pgxscan.Get(
   134  				ctx,
   135  				connectionSource,
   136  				&upserted,
   137  				`SELECT * FROM amms WHERE party_id = $1 AND market_id = $2 AND id = $3 AND amm_party_id = $4`,
   138  				partyID, marketID, poolID, ammPartyID))
   139  			assert.Equal(t, pool, upserted)
   140  		}
   141  	})
   142  
   143  	t.Run("Upsert with empty leverages", func(t *testing.T) {
   144  		upsertTime := block.VegaTime
   145  
   146  		pool := entities.AMMPool{
   147  			PartyID:                        partyID,
   148  			MarketID:                       marketID,
   149  			ID:                             poolID,
   150  			AmmPartyID:                     ammPartyID,
   151  			Commitment:                     num.DecimalFromInt64(100),
   152  			Status:                         entities.AMMStatusActive,
   153  			StatusReason:                   entities.AMMStatusReasonUnspecified,
   154  			ParametersBase:                 num.DecimalFromInt64(1800),
   155  			ParametersLowerBound:           ptr.From(num.DecimalFromInt64(2000)),
   156  			ParametersUpperBound:           ptr.From(num.DecimalFromInt64(2200)),
   157  			ParametersLeverageAtLowerBound: nil,
   158  			ParametersLeverageAtUpperBound: nil,
   159  			CreatedAt:                      block.VegaTime,
   160  			LastUpdated:                    upsertTime,
   161  			LowerVirtualLiquidity:          num.DecimalOne(),
   162  			UpperVirtualLiquidity:          num.DecimalOne(),
   163  			LowerTheoreticalPosition:       num.DecimalOne(),
   164  			UpperTheoreticalPosition:       num.DecimalOne(),
   165  			MinimumPriceChangeTrigger:      num.DecimalOne(),
   166  		}
   167  		require.NoError(t, ps.Upsert(ctx, pool))
   168  		var upserted entities.AMMPool
   169  		require.NoError(t, pgxscan.Get(
   170  			ctx,
   171  			connectionSource,
   172  			&upserted,
   173  			`SELECT * FROM amms WHERE party_id = $1 AND market_id = $2 AND id = $3 AND amm_party_id = $4`,
   174  			partyID, marketID, poolID, ammPartyID))
   175  		assert.Equal(t, pool, upserted)
   176  	})
   177  }
   178  
   179  type partyAccounts struct {
   180  	PartyID    entities.PartyID
   181  	AMMPartyID entities.PartyID
   182  }
   183  
   184  func setupAMMPoolsTest(ctx context.Context, t *testing.T) (
   185  	*sqlstore.AMMPools, []entities.AMMPool, []partyAccounts, []entities.MarketID, []entities.AMMPoolID,
   186  ) {
   187  	t.Helper()
   188  	const (
   189  		partyCount  = 5 // every party will have a derived-party associated for AMM and this derived-party underlies all the AMM pools that are created
   190  		marketCount = 10
   191  		poolCount   = 10
   192  	)
   193  
   194  	bs := sqlstore.NewBlocks(connectionSource)
   195  	ps := sqlstore.NewAMMPools(connectionSource)
   196  
   197  	block := addTestBlock(t, tempTransaction(t), bs)
   198  
   199  	pools := make([]entities.AMMPool, 0, partyCount*marketCount*poolCount)
   200  	parties := make([]partyAccounts, 0, partyCount)
   201  	markets := make([]entities.MarketID, 0, marketCount)
   202  	poolIDs := make([]entities.AMMPoolID, 0, poolCount)
   203  
   204  	for i := 0; i < partyCount; i++ {
   205  		partyID := entities.PartyID(GenerateID())
   206  		ammPartyID := entities.PartyID(GenerateID())
   207  
   208  		parties = append(parties, partyAccounts{PartyID: partyID, AMMPartyID: ammPartyID})
   209  		for j := 0; j < marketCount; j++ {
   210  			marketID := entities.MarketID(GenerateID())
   211  			markets = append(markets, marketID)
   212  			for k := 0; k < poolCount; k++ {
   213  				poolID := entities.AMMPoolID(GenerateID())
   214  				poolIDs = append(poolIDs, poolID)
   215  				status := entities.AMMStatusActive
   216  				statusReason := entities.AMMStatusReasonUnspecified
   217  				if (i+j+k)%2 == 0 {
   218  					status = entities.AMMStatusStopped
   219  					statusReason = entities.AMMStatusReasonCancelledByParty
   220  				}
   221  				pool := entities.AMMPool{
   222  					PartyID:                        partyID,
   223  					MarketID:                       marketID,
   224  					ID:                             poolID,
   225  					AmmPartyID:                     ammPartyID,
   226  					Commitment:                     num.DecimalFromInt64(100),
   227  					Status:                         status,
   228  					StatusReason:                   statusReason,
   229  					ParametersBase:                 num.DecimalFromInt64(100),
   230  					ParametersLowerBound:           ptr.From(num.DecimalFromInt64(100)),
   231  					ParametersUpperBound:           ptr.From(num.DecimalFromInt64(100)),
   232  					ParametersLeverageAtLowerBound: ptr.From(num.DecimalFromInt64(100)),
   233  					ParametersLeverageAtUpperBound: ptr.From(num.DecimalFromInt64(100)),
   234  					CreatedAt:                      block.VegaTime,
   235  					LastUpdated:                    block.VegaTime,
   236  					LowerVirtualLiquidity:          num.DecimalOne(),
   237  					UpperVirtualLiquidity:          num.DecimalOne(),
   238  					LowerTheoreticalPosition:       num.DecimalOne(),
   239  					UpperTheoreticalPosition:       num.DecimalOne(),
   240  					MinimumPriceChangeTrigger:      num.DecimalOne(),
   241  				}
   242  				require.NoError(t, ps.Upsert(ctx, pool))
   243  				pools = append(pools, pool)
   244  			}
   245  		}
   246  	}
   247  
   248  	pools = orderPools(pools)
   249  
   250  	return ps, pools, parties, markets, poolIDs
   251  }
   252  
   253  func orderPools(pools []entities.AMMPool) []entities.AMMPool {
   254  	sort.Slice(pools, func(i, j int) bool {
   255  		return pools[i].CreatedAt.After(pools[j].CreatedAt) ||
   256  			(pools[i].CreatedAt == pools[j].CreatedAt && pools[i].PartyID < pools[j].PartyID) ||
   257  			(pools[i].CreatedAt == pools[j].CreatedAt && pools[i].PartyID == pools[j].PartyID && pools[i].AmmPartyID < pools[j].AmmPartyID) ||
   258  			(pools[i].CreatedAt == pools[j].CreatedAt && pools[i].PartyID == pools[j].PartyID && pools[i].AmmPartyID == pools[j].AmmPartyID && pools[i].MarketID < pools[j].MarketID) ||
   259  			(pools[i].CreatedAt == pools[j].CreatedAt && pools[i].PartyID == pools[j].PartyID && pools[i].AmmPartyID == pools[j].AmmPartyID && pools[i].MarketID == pools[j].MarketID && pools[i].ID <= pools[j].ID)
   260  	})
   261  
   262  	return pools
   263  }
   264  
   265  func TestAMMPools_ListAll(t *testing.T) {
   266  	ctx := tempTransaction(t)
   267  
   268  	ps, pools, _, _, _ := setupAMMPoolsTest(ctx, t)
   269  
   270  	t.Run("Should return all pools if no pagination is provided", func(t *testing.T) {
   271  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   272  		require.NoError(t, err)
   273  		listedPools, pageInfo, err := ps.ListAll(ctx, false, pagination)
   274  		require.NoError(t, err)
   275  		assert.Equal(t, len(pools), len(listedPools))
   276  		assert.Equal(t, pools, listedPools)
   277  		assert.Equal(t, entities.PageInfo{
   278  			HasNextPage:     false,
   279  			HasPreviousPage: false,
   280  			StartCursor:     pools[0].Cursor().Encode(),
   281  			EndCursor:       pools[len(pools)-1].Cursor().Encode(),
   282  		}, pageInfo)
   283  	})
   284  
   285  	t.Run("Should return the first page of pools", func(t *testing.T) {
   286  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), nil, nil, nil, true)
   287  		require.NoError(t, err)
   288  		listedPools, pageInfo, err := ps.ListAll(ctx, false, pagination)
   289  		require.NoError(t, err)
   290  		assert.Equal(t, 5, len(listedPools))
   291  		assert.Equal(t, pools[:5], listedPools)
   292  		assert.Equal(t, entities.PageInfo{
   293  			HasNextPage:     true,
   294  			HasPreviousPage: false,
   295  			StartCursor:     pools[0].Cursor().Encode(),
   296  			EndCursor:       pools[4].Cursor().Encode(),
   297  		}, pageInfo)
   298  	})
   299  
   300  	t.Run("Should return the last page of pools", func(t *testing.T) {
   301  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), nil, true)
   302  		require.NoError(t, err)
   303  		listedPools, pageInfo, err := ps.ListAll(ctx, false, pagination)
   304  		require.NoError(t, err)
   305  		assert.Equal(t, 5, len(listedPools))
   306  		assert.Equal(t, pools[len(pools)-5:], listedPools)
   307  		assert.Equal(t, entities.PageInfo{
   308  			HasNextPage:     false,
   309  			HasPreviousPage: true,
   310  			StartCursor:     pools[len(pools)-5].Cursor().Encode(),
   311  			EndCursor:       pools[len(pools)-1].Cursor().Encode(),
   312  		}, pageInfo)
   313  	})
   314  
   315  	t.Run("Should return the requested page when paging forward", func(t *testing.T) {
   316  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), ptr.From(pools[20].Cursor().Encode()), nil, nil, true)
   317  		require.NoError(t, err)
   318  		listedPools, pageInfo, err := ps.ListAll(ctx, false, pagination)
   319  		require.NoError(t, err)
   320  		assert.Equal(t, 5, len(listedPools))
   321  		assert.Equal(t, pools[21:26], listedPools)
   322  		assert.Equal(t, entities.PageInfo{
   323  			HasNextPage:     true,
   324  			HasPreviousPage: true,
   325  			StartCursor:     pools[21].Cursor().Encode(),
   326  			EndCursor:       pools[25].Cursor().Encode(),
   327  		}, pageInfo)
   328  	})
   329  
   330  	t.Run("Should return the request page when paging backward", func(t *testing.T) {
   331  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), ptr.From(pools[20].Cursor().Encode()), true)
   332  		require.NoError(t, err)
   333  		listedPools, pageInfo, err := ps.ListAll(ctx, false, pagination)
   334  		require.NoError(t, err)
   335  		assert.Equal(t, 5, len(listedPools))
   336  		assert.Equal(t, pools[15:20], listedPools)
   337  		assert.Equal(t, entities.PageInfo{
   338  			HasNextPage:     true,
   339  			HasPreviousPage: true,
   340  			StartCursor:     pools[15].Cursor().Encode(),
   341  			EndCursor:       pools[19].Cursor().Encode(),
   342  		}, pageInfo)
   343  	})
   344  }
   345  
   346  func filterPools(pools []entities.AMMPool, filter func(entities.AMMPool) bool) []entities.AMMPool {
   347  	filtered := make([]entities.AMMPool, 0, len(pools))
   348  	for _, pool := range pools {
   349  		if filter(pool) {
   350  			filtered = append(filtered, pool)
   351  		}
   352  	}
   353  	return filtered
   354  }
   355  
   356  func TestAMMPools_ListByMarket(t *testing.T) {
   357  	ctx := tempTransaction(t)
   358  
   359  	ps, pools, _, markets, _ := setupAMMPoolsTest(ctx, t)
   360  	src := rand.NewSource(time.Now().UnixNano())
   361  	r := rand.New(src)
   362  	n := len(markets)
   363  
   364  	t.Run("Should return all pools if no pagination is provided", func(t *testing.T) {
   365  		// Randomly pick a market
   366  		market := markets[r.Intn(n)]
   367  		want := orderPools(filterPools(pools, func(pool entities.AMMPool) bool {
   368  			return pool.MarketID == market
   369  		}))
   370  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   371  		require.NoError(t, err)
   372  		listedPools, pageInfo, err := ps.ListByMarket(ctx, market, false, pagination)
   373  		require.NoError(t, err)
   374  		assert.Equal(t, len(want), len(listedPools))
   375  		assert.Equal(t, want, listedPools)
   376  		assert.Equal(t, entities.PageInfo{
   377  			HasNextPage:     false,
   378  			HasPreviousPage: false,
   379  			StartCursor:     want[0].Cursor().Encode(),
   380  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   381  		}, pageInfo)
   382  	})
   383  
   384  	t.Run("Should return all active pools", func(t *testing.T) {
   385  		// Randomly pick a market
   386  		market := markets[r.Intn(n)]
   387  		want := orderPools(filterPools(pools, func(pool entities.AMMPool) bool {
   388  			if pool.MarketID != market {
   389  				return false
   390  			}
   391  			return pool.Status == entities.AMMStatusActive || pool.Status == entities.AMMStatusReduceOnly
   392  		}))
   393  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   394  		require.NoError(t, err)
   395  		listedPools, pageInfo, err := ps.ListByMarket(ctx, market, true, pagination)
   396  		require.NoError(t, err)
   397  		assert.Equal(t, len(want), len(listedPools))
   398  
   399  		assert.Equal(t, want, listedPools)
   400  		assert.Equal(t, entities.PageInfo{
   401  			HasNextPage:     false,
   402  			HasPreviousPage: false,
   403  			StartCursor:     want[0].Cursor().Encode(),
   404  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   405  		}, pageInfo)
   406  	})
   407  
   408  	t.Run("Should return the first page of pools", func(t *testing.T) {
   409  		// Randomly pick a market
   410  		market := markets[r.Intn(n)]
   411  		want := orderPools(filterPools(pools, func(pool entities.AMMPool) bool {
   412  			return pool.MarketID == market
   413  		}))
   414  		pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), nil, nil, nil, true)
   415  		require.NoError(t, err)
   416  		listedPools, pageInfo, err := ps.ListByMarket(ctx, market, false, pagination)
   417  		require.NoError(t, err)
   418  		assert.Equal(t, 3, len(listedPools))
   419  		assert.Equal(t, want[:3], listedPools)
   420  		assert.Equal(t, entities.PageInfo{
   421  			HasNextPage:     true,
   422  			HasPreviousPage: false,
   423  			StartCursor:     want[0].Cursor().Encode(),
   424  			EndCursor:       want[2].Cursor().Encode(),
   425  		}, pageInfo)
   426  	})
   427  
   428  	t.Run("Should return the last page of pools", func(t *testing.T) {
   429  		// Randomly pick a market
   430  		market := markets[r.Intn(n)]
   431  		want := orderPools(filterPools(pools, func(pool entities.AMMPool) bool {
   432  			return pool.MarketID == market
   433  		}))
   434  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), nil, true)
   435  		require.NoError(t, err)
   436  		listedPools, pageInfo, err := ps.ListByMarket(ctx, market, false, pagination)
   437  		require.NoError(t, err)
   438  		assert.Equal(t, 3, len(listedPools))
   439  		assert.Equal(t, want[len(want)-3:], listedPools)
   440  		assert.Equal(t, entities.PageInfo{
   441  			HasNextPage:     false,
   442  			HasPreviousPage: true,
   443  			StartCursor:     want[len(want)-3].Cursor().Encode(),
   444  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   445  		}, pageInfo)
   446  	})
   447  
   448  	t.Run("Should return the requested page when paging forward", func(t *testing.T) {
   449  		// Randomly pick a market
   450  		market := markets[r.Intn(n)]
   451  		want := orderPools(filterPools(pools, func(pool entities.AMMPool) bool {
   452  			return pool.MarketID == market
   453  		}))
   454  		pagination, err := entities.NewCursorPagination(ptr.From(int32(3)), ptr.From(want[0].Cursor().Encode()), nil, nil, true)
   455  		require.NoError(t, err)
   456  		listedPools, pageInfo, err := ps.ListByMarket(ctx, market, false, pagination)
   457  		require.NoError(t, err)
   458  		assert.Equal(t, 3, len(listedPools))
   459  		assert.Equal(t, want[1:4], listedPools)
   460  		assert.Equal(t, entities.PageInfo{
   461  			HasNextPage:     true,
   462  			HasPreviousPage: true,
   463  			StartCursor:     want[1].Cursor().Encode(),
   464  			EndCursor:       want[3].Cursor().Encode(),
   465  		}, pageInfo)
   466  	})
   467  
   468  	t.Run("Should return the request page when paging backward", func(t *testing.T) {
   469  		// Randomly pick a market
   470  		market := markets[r.Intn(n)]
   471  		want := orderPools(filterPools(pools, func(pool entities.AMMPool) bool {
   472  			return pool.MarketID == market
   473  		}))
   474  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(3)), ptr.From(want[4].Cursor().Encode()), true)
   475  		require.NoError(t, err)
   476  		listedPools, pageInfo, err := ps.ListByMarket(ctx, market, false, pagination)
   477  		require.NoError(t, err)
   478  		assert.Equal(t, 3, len(listedPools))
   479  		assert.Equal(t, want[1:4], listedPools)
   480  		assert.Equal(t, entities.PageInfo{
   481  			HasNextPage:     true,
   482  			HasPreviousPage: true,
   483  			StartCursor:     want[1].Cursor().Encode(),
   484  			EndCursor:       want[3].Cursor().Encode(),
   485  		}, pageInfo)
   486  	})
   487  }
   488  
   489  func TestAMMPools_ListByParty(t *testing.T) {
   490  	ctx := tempTransaction(t)
   491  
   492  	ps, pools, parties, _, _ := setupAMMPoolsTest(ctx, t)
   493  	src := rand.NewSource(time.Now().UnixNano())
   494  	r := rand.New(src)
   495  	n := len(parties)
   496  
   497  	t.Run("Should return all pools if no pagination is provided", func(t *testing.T) {
   498  		// Randomly pick a party
   499  		party := parties[r.Intn(n)]
   500  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   501  			return pool.PartyID == party.PartyID
   502  		})
   503  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   504  		require.NoError(t, err)
   505  		listedPools, pageInfo, err := ps.ListByParty(ctx, party.PartyID, false, pagination)
   506  		require.NoError(t, err)
   507  		assert.Equal(t, len(want), len(listedPools))
   508  		assert.Equal(t, want, listedPools)
   509  		assert.Equal(t, entities.PageInfo{
   510  			HasNextPage:     false,
   511  			HasPreviousPage: false,
   512  			StartCursor:     want[0].Cursor().Encode(),
   513  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   514  		}, pageInfo)
   515  	})
   516  
   517  	t.Run("Should return the first page of pools", func(t *testing.T) {
   518  		// Randomly pick a party
   519  		party := parties[r.Intn(n)]
   520  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   521  			return pool.PartyID == party.PartyID
   522  		})
   523  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), nil, nil, nil, true)
   524  		require.NoError(t, err)
   525  		listedPools, pageInfo, err := ps.ListByParty(ctx, party.PartyID, false, pagination)
   526  		require.NoError(t, err)
   527  		assert.Equal(t, 5, len(listedPools))
   528  		assert.Equal(t, want[:5], listedPools)
   529  		assert.Equal(t, entities.PageInfo{
   530  			HasNextPage:     true,
   531  			HasPreviousPage: false,
   532  			StartCursor:     want[0].Cursor().Encode(),
   533  			EndCursor:       want[4].Cursor().Encode(),
   534  		}, pageInfo)
   535  	})
   536  
   537  	t.Run("Should return the last page of pools", func(t *testing.T) {
   538  		// Randomly pick a party
   539  		party := parties[r.Intn(n)]
   540  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   541  			return pool.PartyID == party.PartyID
   542  		})
   543  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), nil, true)
   544  		require.NoError(t, err)
   545  		listedPools, pageInfo, err := ps.ListByParty(ctx, party.PartyID, false, pagination)
   546  		require.NoError(t, err)
   547  		assert.Equal(t, 5, len(listedPools))
   548  		assert.Equal(t, want[len(want)-5:], listedPools)
   549  		assert.Equal(t, entities.PageInfo{
   550  			HasNextPage:     false,
   551  			HasPreviousPage: true,
   552  			StartCursor:     want[len(want)-5].Cursor().Encode(),
   553  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   554  		}, pageInfo)
   555  	})
   556  
   557  	t.Run("Should return the requested page when paging forward", func(t *testing.T) {
   558  		// Randomly pick a party
   559  		party := parties[r.Intn(n)]
   560  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   561  			return pool.PartyID == party.PartyID
   562  		})
   563  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), ptr.From(want[10].Cursor().Encode()), nil, nil, true)
   564  		require.NoError(t, err)
   565  		listedPools, pageInfo, err := ps.ListByParty(ctx, party.PartyID, false, pagination)
   566  		require.NoError(t, err)
   567  		assert.Equal(t, 5, len(listedPools))
   568  		assert.Equal(t, want[11:16], listedPools)
   569  		assert.Equal(t, entities.PageInfo{
   570  			HasNextPage:     true,
   571  			HasPreviousPage: true,
   572  			StartCursor:     want[11].Cursor().Encode(),
   573  			EndCursor:       want[15].Cursor().Encode(),
   574  		}, pageInfo)
   575  	})
   576  
   577  	t.Run("Should return the request page when paging backward", func(t *testing.T) {
   578  		// Randomly pick a party
   579  		party := parties[r.Intn(n)]
   580  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   581  			return pool.PartyID == party.PartyID
   582  		})
   583  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), ptr.From(want[10].Cursor().Encode()), true)
   584  		require.NoError(t, err)
   585  		listedPools, pageInfo, err := ps.ListByParty(ctx, party.PartyID, false, pagination)
   586  		require.NoError(t, err)
   587  		assert.Equal(t, 5, len(listedPools))
   588  		assert.Equal(t, want[5:10], listedPools)
   589  		assert.Equal(t, entities.PageInfo{
   590  			HasNextPage:     true,
   591  			HasPreviousPage: true,
   592  			StartCursor:     want[5].Cursor().Encode(),
   593  			EndCursor:       want[9].Cursor().Encode(),
   594  		}, pageInfo)
   595  	})
   596  }
   597  
   598  func TestAMMPools_ListByPartyMarketStatus(t *testing.T) {
   599  	ctx := tempTransaction(t)
   600  
   601  	ps, pools, parties, _, _ := setupAMMPoolsTest(ctx, t)
   602  	src := rand.NewSource(time.Now().UnixNano())
   603  	r := rand.New(src)
   604  	n := len(parties)
   605  
   606  	t.Run("Should list active only", func(t *testing.T) {
   607  		// Randomly pick a party
   608  		party := parties[r.Intn(n)]
   609  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   610  			if pool.PartyID != party.PartyID {
   611  				return false
   612  			}
   613  			return pool.Status == entities.AMMStatusActive || pool.Status == entities.AMMStatusReduceOnly
   614  		})
   615  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   616  		require.NoError(t, err)
   617  		listedPools, pageInfo, err := ps.ListByPartyMarketStatus(ctx, &party.PartyID, nil, nil, true, pagination)
   618  		require.NoError(t, err)
   619  		assert.Equal(t, len(want), len(listedPools))
   620  		assert.Equal(t, want, listedPools)
   621  		assert.Equal(t, entities.PageInfo{
   622  			HasNextPage:     false,
   623  			HasPreviousPage: false,
   624  			StartCursor:     want[0].Cursor().Encode(),
   625  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   626  		}, pageInfo)
   627  	})
   628  }
   629  
   630  func TestAMMPools_ListByPool(t *testing.T) {
   631  	ctx := tempTransaction(t)
   632  
   633  	ps, pools, _, _, poolIDs := setupAMMPoolsTest(ctx, t)
   634  	src := rand.NewSource(time.Now().UnixNano())
   635  	r := rand.New(src)
   636  	n := len(poolIDs)
   637  
   638  	t.Run("Should return the pool if the pool ID exists", func(t *testing.T) {
   639  		pa := poolIDs[r.Intn(n)]
   640  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   641  			return pool.ID == pa
   642  		})
   643  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   644  		require.NoError(t, err)
   645  		listedPools, pageInfo, err := ps.ListByPool(ctx, pa, false, pagination)
   646  		require.NoError(t, err)
   647  		assert.Equal(t, len(want), len(listedPools))
   648  		assert.Equal(t, want, listedPools)
   649  		assert.Equal(t, entities.PageInfo{
   650  			HasNextPage:     false,
   651  			HasPreviousPage: false,
   652  			StartCursor:     want[0].Cursor().Encode(),
   653  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   654  		}, pageInfo)
   655  	})
   656  }
   657  
   658  func TestAMMPools_ListBySubAccount(t *testing.T) {
   659  	ctx := tempTransaction(t)
   660  
   661  	ps, pools, parties, _, _ := setupAMMPoolsTest(ctx, t)
   662  	src := rand.NewSource(time.Now().UnixNano())
   663  	r := rand.New(src)
   664  	n := len(parties)
   665  
   666  	t.Run("Should return all pools if no pagination is provided", func(t *testing.T) {
   667  		// Randomly pick a sub account
   668  		party := parties[r.Intn(n)]
   669  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   670  			return pool.AmmPartyID == party.AMMPartyID
   671  		})
   672  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   673  		require.NoError(t, err)
   674  		listedPools, pageInfo, err := ps.ListBySubAccount(ctx, party.AMMPartyID, false, pagination)
   675  		require.NoError(t, err)
   676  		assert.Equal(t, len(want), len(listedPools))
   677  		assert.Equal(t, want, listedPools)
   678  		assert.Equal(t, entities.PageInfo{
   679  			HasNextPage:     false,
   680  			HasPreviousPage: false,
   681  			StartCursor:     want[0].Cursor().Encode(),
   682  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   683  		}, pageInfo)
   684  	})
   685  
   686  	t.Run("Should return the first page of pools", func(t *testing.T) {
   687  		// Randomly pick a sub account
   688  		party := parties[r.Intn(n)]
   689  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   690  			return pool.AmmPartyID == party.AMMPartyID
   691  		})
   692  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), nil, nil, nil, true)
   693  		require.NoError(t, err)
   694  		listedPools, pageInfo, err := ps.ListBySubAccount(ctx, party.AMMPartyID, false, pagination)
   695  		require.NoError(t, err)
   696  		assert.Equal(t, 5, len(listedPools))
   697  		assert.Equal(t, want[:5], listedPools)
   698  		assert.Equal(t, entities.PageInfo{
   699  			HasNextPage:     true,
   700  			HasPreviousPage: false,
   701  			StartCursor:     want[0].Cursor().Encode(),
   702  			EndCursor:       want[4].Cursor().Encode(),
   703  		}, pageInfo)
   704  	})
   705  
   706  	t.Run("Should return the last page of pools", func(t *testing.T) {
   707  		// Randomly pick a sub account
   708  		party := parties[r.Intn(n)]
   709  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   710  			return pool.AmmPartyID == party.AMMPartyID
   711  		})
   712  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), nil, true)
   713  		require.NoError(t, err)
   714  		listedPools, pageInfo, err := ps.ListBySubAccount(ctx, party.AMMPartyID, false, pagination)
   715  		require.NoError(t, err)
   716  		assert.Equal(t, 5, len(listedPools))
   717  		assert.Equal(t, want[len(want)-5:], listedPools)
   718  		assert.Equal(t, entities.PageInfo{
   719  			HasNextPage:     false,
   720  			HasPreviousPage: true,
   721  			StartCursor:     want[len(want)-5].Cursor().Encode(),
   722  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   723  		}, pageInfo)
   724  	})
   725  
   726  	t.Run("Should return the requested page when paging forward", func(t *testing.T) {
   727  		// Randomly pick a sub account
   728  		party := parties[r.Intn(n)]
   729  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   730  			return pool.AmmPartyID == party.AMMPartyID
   731  		})
   732  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), ptr.From(want[10].Cursor().Encode()), nil, nil, true)
   733  		require.NoError(t, err)
   734  		listedPools, pageInfo, err := ps.ListBySubAccount(ctx, party.AMMPartyID, false, pagination)
   735  		require.NoError(t, err)
   736  		assert.Equal(t, 5, len(listedPools))
   737  		assert.Equal(t, want[11:16], listedPools)
   738  		assert.Equal(t, entities.PageInfo{
   739  			HasNextPage:     true,
   740  			HasPreviousPage: true,
   741  			StartCursor:     want[11].Cursor().Encode(),
   742  			EndCursor:       want[15].Cursor().Encode(),
   743  		}, pageInfo)
   744  	})
   745  
   746  	t.Run("Should return the request page when paging backward", func(t *testing.T) {
   747  		// Randomly pick a sub account
   748  		party := parties[r.Intn(n)]
   749  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   750  			return pool.AmmPartyID == party.AMMPartyID
   751  		})
   752  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), ptr.From(want[10].Cursor().Encode()), true)
   753  		require.NoError(t, err)
   754  		listedPools, pageInfo, err := ps.ListBySubAccount(ctx, party.AMMPartyID, false, pagination)
   755  		require.NoError(t, err)
   756  		assert.Equal(t, 5, len(listedPools))
   757  		assert.Equal(t, want[5:10], listedPools)
   758  		assert.Equal(t, entities.PageInfo{
   759  			HasNextPage:     true,
   760  			HasPreviousPage: true,
   761  			StartCursor:     want[5].Cursor().Encode(),
   762  			EndCursor:       want[9].Cursor().Encode(),
   763  		}, pageInfo)
   764  	})
   765  }
   766  
   767  func TestAMMPools_ListByStatus(t *testing.T) {
   768  	ctx := tempTransaction(t)
   769  
   770  	ps, pools, _, _, _ := setupAMMPoolsTest(ctx, t)
   771  
   772  	t.Run("Should return all pools if no pagination is provided", func(t *testing.T) {
   773  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   774  			return pool.Status == entities.AMMStatusActive
   775  		})
   776  		pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   777  		require.NoError(t, err)
   778  		listedPools, pageInfo, err := ps.ListByStatus(ctx, entities.AMMStatusActive, pagination)
   779  		require.NoError(t, err)
   780  		assert.Equal(t, len(want), len(listedPools))
   781  		assert.Equal(t, want, listedPools)
   782  		assert.Equal(t, entities.PageInfo{
   783  			HasNextPage:     false,
   784  			HasPreviousPage: false,
   785  			StartCursor:     want[0].Cursor().Encode(),
   786  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   787  		}, pageInfo)
   788  	})
   789  
   790  	t.Run("Should return the first page of pools", func(t *testing.T) {
   791  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   792  			return pool.Status == entities.AMMStatusActive
   793  		})
   794  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), nil, nil, nil, true)
   795  		require.NoError(t, err)
   796  		listedPools, pageInfo, err := ps.ListByStatus(ctx, entities.AMMStatusActive, pagination)
   797  		require.NoError(t, err)
   798  		assert.Equal(t, 5, len(listedPools))
   799  		assert.Equal(t, want[:5], listedPools)
   800  		assert.Equal(t, entities.PageInfo{
   801  			HasNextPage:     true,
   802  			HasPreviousPage: false,
   803  			StartCursor:     want[0].Cursor().Encode(),
   804  			EndCursor:       want[4].Cursor().Encode(),
   805  		}, pageInfo)
   806  	})
   807  
   808  	t.Run("Should return the last page of pools", func(t *testing.T) {
   809  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   810  			return pool.Status == entities.AMMStatusActive
   811  		})
   812  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), nil, true)
   813  		require.NoError(t, err)
   814  		listedPools, pageInfo, err := ps.ListByStatus(ctx, entities.AMMStatusActive, pagination)
   815  		require.NoError(t, err)
   816  		assert.Equal(t, 5, len(listedPools))
   817  		assert.Equal(t, want[len(want)-5:], listedPools)
   818  		assert.Equal(t, entities.PageInfo{
   819  			HasNextPage:     false,
   820  			HasPreviousPage: true,
   821  			StartCursor:     want[len(want)-5].Cursor().Encode(),
   822  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   823  		}, pageInfo)
   824  	})
   825  
   826  	t.Run("Should return the requested page when paging forward", func(t *testing.T) {
   827  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   828  			return pool.Status == entities.AMMStatusActive
   829  		})
   830  		pagination, err := entities.NewCursorPagination(ptr.From(int32(5)), ptr.From(want[10].Cursor().Encode()), nil, nil, true)
   831  		require.NoError(t, err)
   832  		listedPools, pageInfo, err := ps.ListByStatus(ctx, entities.AMMStatusActive, pagination)
   833  		require.NoError(t, err)
   834  		assert.Equal(t, 5, len(listedPools))
   835  		assert.Equal(t, want[11:16], listedPools)
   836  		assert.Equal(t, entities.PageInfo{
   837  			HasNextPage:     true,
   838  			HasPreviousPage: true,
   839  			StartCursor:     want[11].Cursor().Encode(),
   840  			EndCursor:       want[15].Cursor().Encode(),
   841  		}, pageInfo)
   842  	})
   843  
   844  	t.Run("Should return the request page when paging backward", func(t *testing.T) {
   845  		want := filterPools(pools, func(pool entities.AMMPool) bool {
   846  			return pool.Status == entities.AMMStatusActive
   847  		})
   848  		pagination, err := entities.NewCursorPagination(nil, nil, ptr.From(int32(5)), ptr.From(want[10].Cursor().Encode()), true)
   849  		require.NoError(t, err)
   850  		listedPools, pageInfo, err := ps.ListByStatus(ctx, entities.AMMStatusActive, pagination)
   851  		require.NoError(t, err)
   852  		assert.Equal(t, 5, len(listedPools))
   853  		assert.Equal(t, want[5:10], listedPools)
   854  		assert.Equal(t, entities.PageInfo{
   855  			HasNextPage:     true,
   856  			HasPreviousPage: true,
   857  			StartCursor:     want[5].Cursor().Encode(),
   858  			EndCursor:       want[9].Cursor().Encode(),
   859  		}, pageInfo)
   860  	})
   861  }
   862  
   863  func TestAMMPools_ListActive(t *testing.T) {
   864  	ctx := tempTransaction(t)
   865  
   866  	ps, in, _, _, _ := setupAMMPoolsTest(ctx, t)
   867  	var nActive int
   868  	for _, p := range in {
   869  		if p.Status == entities.AMMStatusActive || p.Status == entities.AMMStatusReduceOnly {
   870  			nActive++
   871  		}
   872  	}
   873  	require.NotEqual(t, 0, nActive)
   874  
   875  	out, err := ps.ListActive(ctx)
   876  	require.NoError(t, err)
   877  	assert.Equal(t, nActive, len(out))
   878  }