code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/funding_period_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  	"sort"
    21  	"testing"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/datanode/entities"
    25  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    26  	"code.vegaprotocol.io/vega/datanode/sqlstore/helpers"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  	"code.vegaprotocol.io/vega/libs/ptr"
    29  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    30  
    31  	"github.com/georgysavva/scany/pgxscan"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  type fundingPeriodTestStores struct {
    37  	bs *sqlstore.Blocks
    38  	ms *sqlstore.Markets
    39  	fp *sqlstore.FundingPeriods
    40  
    41  	blocks     []entities.Block
    42  	markets    []entities.Market
    43  	periods    []entities.FundingPeriod
    44  	dataPoints []entities.FundingPeriodDataPoint
    45  }
    46  
    47  func setupFundingPeriodTests(ctx context.Context, t *testing.T) *fundingPeriodTestStores {
    48  	t.Helper()
    49  	bs := sqlstore.NewBlocks(connectionSource)
    50  	ms := sqlstore.NewMarkets(connectionSource)
    51  	fp := sqlstore.NewFundingPeriods(connectionSource)
    52  
    53  	return newFundingPeriodTestStores(bs, ms, fp).Initialize(ctx, t)
    54  }
    55  
    56  func newFundingPeriodTestStores(bs *sqlstore.Blocks, ms *sqlstore.Markets, fp *sqlstore.FundingPeriods) *fundingPeriodTestStores {
    57  	return &fundingPeriodTestStores{
    58  		bs: bs,
    59  		ms: ms,
    60  		fp: fp,
    61  	}
    62  }
    63  
    64  func (s *fundingPeriodTestStores) Initialize(ctx context.Context, t *testing.T) *fundingPeriodTestStores {
    65  	t.Helper()
    66  	s.blocks = make([]entities.Block, 0, 10)
    67  	s.markets = make([]entities.Market, 0, 3)
    68  
    69  	for i := 0; i < 10; i++ {
    70  		block := addTestBlock(t, ctx, s.bs)
    71  		s.blocks = append(s.blocks, block)
    72  		if i < 3 {
    73  			s.markets = append(s.markets, helpers.AddTestMarket(t, ctx, s.ms, block))
    74  		}
    75  	}
    76  
    77  	return s
    78  }
    79  
    80  func TestFundingPeriod_AddFundingPeriod(t *testing.T) {
    81  	t.Run("should add funding period if the market exists and the sequence number does not exist", testAddFundingPeriodShouldSucceedIfMarketExistsAndSequenceDoesNotExist)
    82  	t.Run("should update funding period if the market exists and the sequence number already exists", testAddFundingPeriodShouldUpdateIfMarketExistsAndSequenceExists)
    83  }
    84  
    85  func testAddFundingPeriodShouldSucceedIfMarketExistsAndSequenceDoesNotExist(t *testing.T) {
    86  	ctx := tempTransaction(t)
    87  
    88  	stores := setupFundingPeriodTests(ctx, t)
    89  
    90  	period := entities.FundingPeriod{
    91  		MarketID:         stores.markets[0].ID,
    92  		FundingPeriodSeq: 1,
    93  		StartTime:        stores.blocks[3].VegaTime,
    94  		EndTime:          nil,
    95  		FundingPayment:   nil,
    96  		FundingRate:      nil,
    97  		VegaTime:         stores.blocks[3].VegaTime,
    98  		TxHash:           generateTxHash(),
    99  	}
   100  
   101  	err := stores.fp.AddFundingPeriod(ctx, &period)
   102  	require.NoError(t, err)
   103  }
   104  
   105  func testAddFundingPeriodShouldUpdateIfMarketExistsAndSequenceExists(t *testing.T) {
   106  	ctx := tempTransaction(t)
   107  
   108  	stores := setupFundingPeriodTests(ctx, t)
   109  
   110  	period := entities.FundingPeriod{
   111  		MarketID:         stores.markets[0].ID,
   112  		FundingPeriodSeq: 1,
   113  		StartTime:        stores.blocks[3].VegaTime,
   114  		EndTime:          nil,
   115  		FundingPayment:   nil,
   116  		FundingRate:      nil,
   117  		ExternalTwap:     nil,
   118  		InternalTwap:     nil,
   119  		VegaTime:         stores.blocks[3].VegaTime,
   120  		TxHash:           generateTxHash(),
   121  	}
   122  
   123  	err := stores.fp.AddFundingPeriod(ctx, &period)
   124  	require.NoError(t, err)
   125  
   126  	var dbResult entities.FundingPeriod
   127  	err = pgxscan.Get(ctx, stores.fp, &dbResult, `select * from funding_period where market_id = $1 and funding_period_seq = $2`, stores.markets[0].ID, 1)
   128  	require.NoError(t, err)
   129  	assert.Equal(t, period, dbResult)
   130  
   131  	period.EndTime = &stores.blocks[9].VegaTime
   132  	period.FundingPayment = ptr.From(num.DecimalFromFloat(1.0))
   133  	period.FundingRate = ptr.From(num.DecimalFromFloat(1.0))
   134  	period.ExternalTwap = ptr.From(num.DecimalFromFloat(1.0))
   135  	period.InternalTwap = ptr.From(num.DecimalFromFloat(1.1))
   136  	period.VegaTime = stores.blocks[9].VegaTime
   137  	period.TxHash = generateTxHash()
   138  
   139  	err = stores.fp.AddFundingPeriod(ctx, &period)
   140  	require.NoError(t, err)
   141  
   142  	err = pgxscan.Get(ctx, stores.fp, &dbResult, `select * from funding_period where market_id = $1 and funding_period_seq = $2`, stores.markets[0].ID, 1)
   143  	require.NoError(t, err)
   144  	assert.Equal(t, period, dbResult)
   145  }
   146  
   147  func TestFundingPeriod_AddFundingPeriodDataPoint(t *testing.T) {
   148  	t.Run("should add data points for existing funding periods", testAddForExistingFundingPeriods)
   149  	t.Run("should not error if the funding period does not exist", testShouldNotErrorIfNoFundingPeriod)
   150  	t.Run("should update the data point if multiple data points for the same source is received in the same block", testShouldUpdateDataPointInSameBlock)
   151  }
   152  
   153  func testAddForExistingFundingPeriods(t *testing.T) {
   154  	ctx := tempTransaction(t)
   155  
   156  	stores := setupFundingPeriodTests(ctx, t)
   157  
   158  	period := entities.FundingPeriod{
   159  		MarketID:         stores.markets[0].ID,
   160  		FundingPeriodSeq: 1,
   161  		StartTime:        stores.blocks[3].VegaTime,
   162  		EndTime:          nil,
   163  		FundingPayment:   nil,
   164  		FundingRate:      nil,
   165  		VegaTime:         stores.blocks[3].VegaTime,
   166  		TxHash:           generateTxHash(),
   167  	}
   168  
   169  	err := stores.fp.AddFundingPeriod(ctx, &period)
   170  	require.NoError(t, err)
   171  
   172  	dataPoint := entities.FundingPeriodDataPoint{
   173  		MarketID:         stores.markets[0].ID,
   174  		FundingPeriodSeq: 1,
   175  		DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   176  		Price:            num.DecimalFromFloat(1.0),
   177  		Timestamp:        stores.blocks[4].VegaTime,
   178  		VegaTime:         stores.blocks[4].VegaTime,
   179  		TxHash:           generateTxHash(),
   180  	}
   181  
   182  	err = stores.fp.AddDataPoint(ctx, &dataPoint)
   183  	require.NoError(t, err)
   184  }
   185  
   186  func testShouldNotErrorIfNoFundingPeriod(t *testing.T) {
   187  	// Note: this test was changed from should error to should not error as we can not rely on the
   188  	// foreign key constraint to the funding_period table which has been dropped due to the
   189  	// funding_period_data_point table being migrated to a TimescaleDB hypertable.
   190  	ctx := tempTransaction(t)
   191  
   192  	stores := setupFundingPeriodTests(ctx, t)
   193  
   194  	dataPoint := entities.FundingPeriodDataPoint{
   195  		MarketID:         stores.markets[0].ID,
   196  		FundingPeriodSeq: 2,
   197  		DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   198  		Price:            num.DecimalFromFloat(100.0),
   199  		Timestamp:        stores.blocks[4].VegaTime,
   200  		VegaTime:         stores.blocks[4].VegaTime,
   201  		TxHash:           generateTxHash(),
   202  	}
   203  
   204  	err := stores.fp.AddDataPoint(ctx, &dataPoint)
   205  	require.NoError(t, err)
   206  }
   207  
   208  func testShouldUpdateDataPointInSameBlock(t *testing.T) {
   209  	ctx := tempTransaction(t)
   210  
   211  	stores := setupFundingPeriodTests(ctx, t)
   212  
   213  	period := entities.FundingPeriod{
   214  		MarketID:         stores.markets[0].ID,
   215  		FundingPeriodSeq: 1,
   216  		StartTime:        stores.blocks[3].VegaTime,
   217  		EndTime:          nil,
   218  		FundingPayment:   nil,
   219  		FundingRate:      nil,
   220  		ExternalTwap:     nil,
   221  		InternalTwap:     nil,
   222  		VegaTime:         stores.blocks[3].VegaTime,
   223  		TxHash:           generateTxHash(),
   224  	}
   225  
   226  	err := stores.fp.AddFundingPeriod(ctx, &period)
   227  	require.NoError(t, err)
   228  
   229  	dp1 := entities.FundingPeriodDataPoint{
   230  		MarketID:         stores.markets[0].ID,
   231  		FundingPeriodSeq: 1,
   232  		DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   233  		Price:            num.DecimalFromFloat(1.0),
   234  		Twap:             num.DecimalFromFloat(1.0),
   235  		Timestamp:        stores.blocks[4].VegaTime,
   236  		VegaTime:         stores.blocks[4].VegaTime,
   237  		TxHash:           generateTxHash(),
   238  	}
   239  
   240  	err = stores.fp.AddDataPoint(ctx, &dp1)
   241  	require.NoError(t, err)
   242  
   243  	var inserted []entities.FundingPeriodDataPoint
   244  	err = pgxscan.Select(ctx, connectionSource, &inserted,
   245  		`SELECT * FROM funding_period_data_points where market_id = $1 and funding_period_seq = $2 and data_point_type = $3 and vega_time = $4`,
   246  		stores.markets[0].ID, 1, entities.FundingPeriodDataPointSourceExternal, stores.blocks[4].VegaTime)
   247  	require.NoError(t, err)
   248  	assert.Len(t, inserted, 1)
   249  	assert.Equal(t, dp1, inserted[0])
   250  
   251  	dp2 := entities.FundingPeriodDataPoint{
   252  		MarketID:         stores.markets[0].ID,
   253  		FundingPeriodSeq: 1,
   254  		DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   255  		Price:            num.DecimalFromFloat(2.0),
   256  		Twap:             num.DecimalFromFloat(2.0),
   257  		Timestamp:        stores.blocks[4].VegaTime.Add(100 * time.Microsecond),
   258  		VegaTime:         stores.blocks[4].VegaTime,
   259  		TxHash:           generateTxHash(),
   260  	}
   261  
   262  	err = stores.fp.AddDataPoint(ctx, &dp2)
   263  	require.NoError(t, err)
   264  
   265  	err = pgxscan.Select(ctx, connectionSource, &inserted,
   266  		`SELECT * FROM funding_period_data_points where market_id = $1 and funding_period_seq = $2 and data_point_type = $3 and vega_time = $4`,
   267  		stores.markets[0].ID, 1, entities.FundingPeriodDataPointSourceExternal, stores.blocks[4].VegaTime)
   268  	require.NoError(t, err)
   269  	assert.Len(t, inserted, 1)
   270  	assert.Equal(t, dp2, inserted[0])
   271  }
   272  
   273  func addFundingPeriodsAndDataPoints(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   274  	t.Helper()
   275  	stores.periods = []entities.FundingPeriod{
   276  		{
   277  			MarketID:         stores.markets[0].ID,
   278  			FundingPeriodSeq: 1,
   279  			StartTime:        stores.blocks[1].VegaTime,
   280  			EndTime:          nil,
   281  			FundingPayment:   ptr.From(num.DecimalFromFloat(1)),
   282  			FundingRate:      ptr.From(num.DecimalFromFloat(1)),
   283  			ExternalTwap:     ptr.From(num.DecimalFromFloat(1)),
   284  			InternalTwap:     ptr.From(num.DecimalFromFloat(1)),
   285  			VegaTime:         stores.blocks[1].VegaTime,
   286  			TxHash:           generateTxHash(),
   287  		},
   288  		{
   289  			MarketID:         stores.markets[0].ID,
   290  			FundingPeriodSeq: 2,
   291  			StartTime:        stores.blocks[3].VegaTime,
   292  			EndTime:          nil,
   293  			FundingPayment:   ptr.From(num.DecimalFromFloat(2)),
   294  			FundingRate:      ptr.From(num.DecimalFromFloat(2)),
   295  			ExternalTwap:     ptr.From(num.DecimalFromFloat(2)),
   296  			InternalTwap:     ptr.From(num.DecimalFromFloat(2)),
   297  			VegaTime:         stores.blocks[3].VegaTime,
   298  			TxHash:           generateTxHash(),
   299  		},
   300  		{
   301  			MarketID:         stores.markets[0].ID,
   302  			FundingPeriodSeq: 3,
   303  			StartTime:        stores.blocks[5].VegaTime,
   304  			EndTime:          nil,
   305  			FundingPayment:   ptr.From(num.DecimalFromFloat(3)),
   306  			FundingRate:      ptr.From(num.DecimalFromFloat(3)),
   307  			ExternalTwap:     ptr.From(num.DecimalFromFloat(3)),
   308  			InternalTwap:     ptr.From(num.DecimalFromFloat(3)),
   309  			VegaTime:         stores.blocks[5].VegaTime,
   310  			TxHash:           generateTxHash(),
   311  		},
   312  		{
   313  			MarketID:         stores.markets[0].ID,
   314  			FundingPeriodSeq: 4,
   315  			StartTime:        stores.blocks[7].VegaTime,
   316  			EndTime:          nil,
   317  			FundingPayment:   ptr.From(num.DecimalFromFloat(5)),
   318  			FundingRate:      ptr.From(num.DecimalFromFloat(5)),
   319  			ExternalTwap:     ptr.From(num.DecimalFromFloat(5)),
   320  			InternalTwap:     ptr.From(num.DecimalFromFloat(5)),
   321  			VegaTime:         stores.blocks[7].VegaTime,
   322  			TxHash:           generateTxHash(),
   323  		},
   324  		{
   325  			MarketID:         stores.markets[0].ID,
   326  			FundingPeriodSeq: 5,
   327  			StartTime:        stores.blocks[9].VegaTime,
   328  			EndTime:          nil,
   329  			FundingPayment:   ptr.From(num.DecimalFromFloat(5)),
   330  			FundingRate:      ptr.From(num.DecimalFromFloat(5)),
   331  			ExternalTwap:     ptr.From(num.DecimalFromFloat(5)),
   332  			InternalTwap:     ptr.From(num.DecimalFromFloat(5)),
   333  			VegaTime:         stores.blocks[9].VegaTime,
   334  			TxHash:           generateTxHash(),
   335  		},
   336  	}
   337  
   338  	stores.dataPoints = []entities.FundingPeriodDataPoint{
   339  		{
   340  			MarketID:         stores.markets[0].ID,
   341  			FundingPeriodSeq: 1,
   342  			DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   343  			Price:            num.DecimalFromFloat(1.0),
   344  			Twap:             num.DecimalFromFloat(1.0),
   345  			Timestamp:        stores.blocks[2].VegaTime,
   346  			VegaTime:         stores.blocks[2].VegaTime,
   347  			TxHash:           generateTxHash(),
   348  		},
   349  		{
   350  			MarketID:         stores.markets[0].ID,
   351  			FundingPeriodSeq: 1,
   352  			DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   353  			Price:            num.DecimalFromFloat(1.0),
   354  			Twap:             num.DecimalFromFloat(1.0),
   355  			Timestamp:        stores.blocks[3].VegaTime,
   356  			VegaTime:         stores.blocks[3].VegaTime,
   357  			TxHash:           generateTxHash(),
   358  		},
   359  		{
   360  			MarketID:         stores.markets[0].ID,
   361  			FundingPeriodSeq: 1,
   362  			DataPointType:    entities.FundingPeriodDataPointSourceInternal,
   363  			Price:            num.DecimalFromFloat(1.0),
   364  			Twap:             num.DecimalFromFloat(1.0),
   365  			Timestamp:        stores.blocks[4].VegaTime,
   366  			VegaTime:         stores.blocks[4].VegaTime,
   367  			TxHash:           generateTxHash(),
   368  		},
   369  		{
   370  			MarketID:         stores.markets[0].ID,
   371  			FundingPeriodSeq: 2,
   372  			DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   373  			Price:            num.DecimalFromFloat(1.0),
   374  			Twap:             num.DecimalFromFloat(1.0),
   375  			Timestamp:        stores.blocks[5].VegaTime,
   376  			VegaTime:         stores.blocks[5].VegaTime,
   377  			TxHash:           generateTxHash(),
   378  		},
   379  		{
   380  			MarketID:         stores.markets[0].ID,
   381  			FundingPeriodSeq: 3,
   382  			DataPointType:    entities.FundingPeriodDataPointSourceExternal,
   383  			Price:            num.DecimalFromFloat(1.0),
   384  			Twap:             num.DecimalFromFloat(1.0),
   385  			Timestamp:        stores.blocks[6].VegaTime,
   386  			VegaTime:         stores.blocks[6].VegaTime,
   387  			TxHash:           generateTxHash(),
   388  		},
   389  	}
   390  
   391  	for _, period := range stores.periods {
   392  		err := stores.fp.AddFundingPeriod(ctx, &period)
   393  		require.NoError(t, err)
   394  	}
   395  
   396  	for _, dataPoint := range stores.dataPoints {
   397  		err := stores.fp.AddDataPoint(ctx, &dataPoint)
   398  		require.NoError(t, err)
   399  	}
   400  
   401  	// Let's make sure the data is ordered correctly just in case we add data points, but not in order
   402  	sort.Slice(stores.periods, func(i, j int) bool {
   403  		return stores.periods[i].VegaTime.After(stores.periods[j].VegaTime) ||
   404  			stores.periods[i].MarketID < stores.periods[j].MarketID ||
   405  			stores.periods[i].FundingPeriodSeq < stores.periods[j].FundingPeriodSeq
   406  	})
   407  
   408  	sort.Slice(stores.dataPoints, func(i, j int) bool {
   409  		return stores.dataPoints[i].VegaTime.After(stores.dataPoints[j].VegaTime) ||
   410  			stores.dataPoints[i].MarketID < stores.dataPoints[j].MarketID ||
   411  			stores.dataPoints[i].FundingPeriodSeq < stores.dataPoints[j].FundingPeriodSeq ||
   412  			stores.dataPoints[i].DataPointType < stores.dataPoints[j].DataPointType
   413  	})
   414  }
   415  
   416  func TestFundingPeriodListFundingPeriods(t *testing.T) {
   417  	ctx := tempTransaction(t)
   418  
   419  	stores := setupFundingPeriodTests(ctx, t)
   420  
   421  	addFundingPeriodsAndDataPoints(t, ctx, stores)
   422  
   423  	t.Run("should return the first page of funding periods when no sequence number is given", func(t *testing.T) {
   424  		testListFundingPeriodsMarketNoSequence(t, ctx, stores)
   425  	})
   426  	t.Run("should return the specific funding period when the market and sequence number is given", func(t *testing.T) {
   427  		testListFundingPeriodForMarketSequence(t, ctx, stores)
   428  	})
   429  	t.Run("should return the page of funding periods when pagination control is provided", func(t *testing.T) {
   430  		testListFundingPeriodPagination(t, ctx, stores)
   431  	})
   432  }
   433  
   434  func testListFundingPeriodsMarketNoSequence(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   435  	t.Helper()
   436  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   437  	require.NoError(t, err)
   438  
   439  	dateRange := entities.DateRange{}
   440  
   441  	got, pageInfo, err := stores.fp.ListFundingPeriods(ctx, stores.markets[0].ID, dateRange, pagination)
   442  	require.NoError(t, err)
   443  	want := stores.periods
   444  
   445  	assert.Equal(t, want, got)
   446  	assert.Equal(t, entities.PageInfo{
   447  		HasNextPage:     false,
   448  		HasPreviousPage: false,
   449  		StartCursor:     want[0].Cursor().Encode(),
   450  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   451  	}, pageInfo)
   452  }
   453  
   454  func testListFundingPeriodForMarketSequence(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   455  	t.Helper()
   456  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   457  	require.NoError(t, err)
   458  
   459  	dateRange := entities.DateRange{
   460  		End: ptr.From(stores.periods[3].StartTime),
   461  	}
   462  	got, pageInfo, err := stores.fp.ListFundingPeriods(ctx, stores.markets[0].ID, dateRange, pagination)
   463  	require.NoError(t, err)
   464  	want := stores.periods[4:]
   465  
   466  	assert.Equal(t, want, got)
   467  	assert.Equal(t, entities.PageInfo{
   468  		HasNextPage:     false,
   469  		HasPreviousPage: false,
   470  		StartCursor:     want[0].Cursor().Encode(),
   471  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   472  	}, pageInfo)
   473  }
   474  
   475  func testListFundingPeriodPagination(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   476  	t.Helper()
   477  	var first, last int32
   478  	var after, before string
   479  
   480  	t.Run("should return the first page when first is specified with no cursor", func(t *testing.T) {
   481  		first = 2
   482  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   483  		require.NoError(t, err)
   484  
   485  		dateRange := entities.DateRange{}
   486  		got, pageInfo, err := stores.fp.ListFundingPeriods(ctx, stores.markets[0].ID, dateRange, pagination)
   487  		require.NoError(t, err)
   488  		want := stores.periods[:2]
   489  
   490  		assert.Equal(t, want, got)
   491  		assert.Equal(t, entities.PageInfo{
   492  			HasNextPage:     true,
   493  			HasPreviousPage: false,
   494  			StartCursor:     want[0].Cursor().Encode(),
   495  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   496  		}, pageInfo)
   497  	})
   498  
   499  	t.Run("should return the first page after the cursor when first is specified with a cursor", func(t *testing.T) {
   500  		first = 2
   501  		after = stores.periods[1].Cursor().Encode()
   502  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   503  		require.NoError(t, err)
   504  		dateRange := entities.DateRange{}
   505  		got, pageInfo, err := stores.fp.ListFundingPeriods(ctx, stores.markets[0].ID, dateRange, pagination)
   506  		require.NoError(t, err)
   507  		want := stores.periods[2:4]
   508  		assert.Equal(t, want, got)
   509  		assert.Equal(t, entities.PageInfo{
   510  			HasNextPage:     true,
   511  			HasPreviousPage: true,
   512  			StartCursor:     want[0].Cursor().Encode(),
   513  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   514  		}, pageInfo)
   515  	})
   516  
   517  	t.Run("should return the last page when last is specified with no cursor", func(t *testing.T) {
   518  		last = 2
   519  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   520  		require.NoError(t, err)
   521  		dateRange := entities.DateRange{}
   522  		got, pageInfo, err := stores.fp.ListFundingPeriods(ctx, stores.markets[0].ID, dateRange, pagination)
   523  		require.NoError(t, err)
   524  		want := stores.periods[len(stores.periods)-2:]
   525  		assert.Equal(t, want, got)
   526  		assert.Equal(t, entities.PageInfo{
   527  			HasNextPage:     false,
   528  			HasPreviousPage: true,
   529  			StartCursor:     want[0].Cursor().Encode(),
   530  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   531  		}, pageInfo)
   532  	})
   533  
   534  	t.Run("should return the last page before the cursor when last is specified with a cursor", func(t *testing.T) {
   535  		last = 2
   536  		before = stores.periods[3].Cursor().Encode()
   537  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   538  		require.NoError(t, err)
   539  		dateRange := entities.DateRange{}
   540  		got, pageInfo, err := stores.fp.ListFundingPeriods(ctx, stores.markets[0].ID, dateRange, pagination)
   541  		require.NoError(t, err)
   542  		want := stores.periods[1:3]
   543  		assert.Equal(t, want, got)
   544  		assert.Equal(t, entities.PageInfo{
   545  			HasNextPage:     true,
   546  			HasPreviousPage: true,
   547  			StartCursor:     want[0].Cursor().Encode(),
   548  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   549  		}, pageInfo)
   550  	})
   551  }
   552  
   553  func TestFundingPeriod_ListDataPoints(t *testing.T) {
   554  	ctx := tempTransaction(t)
   555  
   556  	stores := setupFundingPeriodTests(ctx, t)
   557  
   558  	addFundingPeriodsAndDataPoints(t, ctx, stores)
   559  
   560  	t.Run("Should return the first page of data points for all sequences if none are specified", func(t *testing.T) {
   561  		testListDataPointsAllSequences(t, ctx, stores)
   562  	})
   563  	t.Run("Should return the first page of data points for specified sequences only", func(t *testing.T) {
   564  		testListDataPointsSpecifiedSequences(t, ctx, stores)
   565  	})
   566  	t.Run("should return the first page of data points when no source is specified", func(t *testing.T) {
   567  		testListDataPointsNoSource(t, ctx, stores)
   568  	})
   569  	t.Run("should return the first page of data points for the specified source", func(t *testing.T) {
   570  		testListDataPointsForSource(t, ctx, stores)
   571  	})
   572  	t.Run("should return the page of data points when pagination control is provided", func(t *testing.T) {
   573  		testListDataPointsPagination(t, ctx, stores)
   574  	})
   575  }
   576  
   577  func testListDataPointsAllSequences(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   578  	t.Helper()
   579  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   580  	require.NoError(t, err)
   581  	dateRange := entities.DateRange{}
   582  	got, pageInfo, err := stores.fp.ListFundingPeriodDataPoints(ctx, stores.markets[0].ID, dateRange, nil, nil, pagination)
   583  	require.NoError(t, err)
   584  	want := stores.dataPoints[:]
   585  	assert.Equal(t, want, got)
   586  	assert.Equal(t, entities.PageInfo{
   587  		HasNextPage:     false,
   588  		HasPreviousPage: false,
   589  		StartCursor:     want[0].Cursor().Encode(),
   590  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   591  	}, pageInfo)
   592  }
   593  
   594  func testListDataPointsSpecifiedSequences(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   595  	t.Helper()
   596  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   597  	require.NoError(t, err)
   598  	dateRange := entities.DateRange{}
   599  	got, pageInfo, err := stores.fp.ListFundingPeriodDataPoints(ctx, stores.markets[0].ID, dateRange, nil, nil, pagination)
   600  	require.NoError(t, err)
   601  	want := stores.dataPoints[:]
   602  	assert.Equal(t, want, got)
   603  	assert.Equal(t, entities.PageInfo{
   604  		HasNextPage:     false,
   605  		HasPreviousPage: false,
   606  		StartCursor:     want[0].Cursor().Encode(),
   607  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   608  	}, pageInfo)
   609  }
   610  
   611  func testListDataPointsNoSource(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   612  	t.Helper()
   613  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   614  	require.NoError(t, err)
   615  	want := stores.dataPoints[2:]
   616  	wantPageInfo := entities.PageInfo{
   617  		HasNextPage:     false,
   618  		HasPreviousPage: false,
   619  		StartCursor:     want[0].Cursor().Encode(),
   620  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   621  	}
   622  	// Seq 1
   623  	dateRange := entities.DateRange{
   624  		Start: ptr.From(stores.dataPoints[4].Timestamp),
   625  		End:   ptr.From(stores.dataPoints[1].Timestamp),
   626  	}
   627  	testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange, nil, nil, pagination, want, wantPageInfo)
   628  	want = stores.dataPoints[1:]
   629  	wantPageInfo = entities.PageInfo{
   630  		HasNextPage:     false,
   631  		HasPreviousPage: false,
   632  		StartCursor:     want[0].Cursor().Encode(),
   633  		EndCursor:       want[len(want)-1].Cursor().Encode(),
   634  	}
   635  	// Seq 1 and 2
   636  	dateRange = entities.DateRange{
   637  		Start: ptr.From(stores.dataPoints[4].Timestamp),
   638  		End:   ptr.From(stores.dataPoints[0].Timestamp),
   639  	}
   640  	testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange, nil, nil, pagination, want, wantPageInfo)
   641  }
   642  
   643  func testFundingPeriodsListDataPoints(t *testing.T, ctx context.Context, fp *sqlstore.FundingPeriods, marketID entities.MarketID, dateRange entities.DateRange,
   644  	source *entities.FundingPeriodDataPointSource, seq *uint64, pagination entities.CursorPagination, want []entities.FundingPeriodDataPoint,
   645  	wantPageInfo entities.PageInfo,
   646  ) {
   647  	t.Helper()
   648  	got, pageInfo, err := fp.ListFundingPeriodDataPoints(ctx, marketID, dateRange, source, seq, pagination)
   649  	require.NoError(t, err)
   650  	assert.Equal(t, want, got)
   651  	assert.Equal(t, wantPageInfo, pageInfo)
   652  }
   653  
   654  func testListDataPointsForSource(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   655  	t.Helper()
   656  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   657  	require.NoError(t, err)
   658  
   659  	t.Run("should filter by just source", func(t *testing.T) {
   660  		want := []entities.FundingPeriodDataPoint{}
   661  		want = append(want, stores.dataPoints[:2]...)
   662  		want = append(want, stores.dataPoints[3:]...)
   663  		wantPageInfo := entities.PageInfo{
   664  			HasNextPage:     false,
   665  			HasPreviousPage: false,
   666  			StartCursor:     want[0].Cursor().Encode(),
   667  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   668  		}
   669  		dateRange := entities.DateRange{}
   670  		testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange,
   671  			ptr.From(entities.FundingPeriodDataPointSourceExternal), nil, pagination, want, wantPageInfo)
   672  	})
   673  
   674  	t.Run("should filter by date range and source", func(t *testing.T) {
   675  		want := []entities.FundingPeriodDataPoint{}
   676  		want = append(want, stores.dataPoints[1])
   677  		want = append(want, stores.dataPoints[3:]...)
   678  		wantPageInfo := entities.PageInfo{
   679  			HasNextPage:     false,
   680  			HasPreviousPage: false,
   681  			StartCursor:     want[0].Cursor().Encode(),
   682  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   683  		}
   684  		// sequence 1 and 2
   685  		dateRange := entities.DateRange{
   686  			Start: ptr.From(stores.dataPoints[4].Timestamp),
   687  			End:   ptr.From(stores.dataPoints[0].Timestamp),
   688  		}
   689  		testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange,
   690  			ptr.From(entities.FundingPeriodDataPointSourceExternal), nil, pagination, want, wantPageInfo)
   691  	})
   692  	t.Run("should filter by seq", func(t *testing.T) {
   693  		want := []entities.FundingPeriodDataPoint{}
   694  		want = append(want, stores.dataPoints[1])
   695  		wantPageInfo := entities.PageInfo{
   696  			HasNextPage:     false,
   697  			HasPreviousPage: false,
   698  			StartCursor:     want[0].Cursor().Encode(),
   699  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   700  		}
   701  		dateRange := entities.DateRange{}
   702  		// sequence 2 only, which is one point
   703  		testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange,
   704  			nil, ptr.From(uint64(2)), pagination, want, wantPageInfo)
   705  	})
   706  }
   707  
   708  func testListDataPointsPagination(t *testing.T, ctx context.Context, stores *fundingPeriodTestStores) {
   709  	t.Helper()
   710  	var first, last int32
   711  	var after, before string
   712  
   713  	t.Run("should return the first page when first is specified with no cursor", func(t *testing.T) {
   714  		first = 2
   715  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   716  		require.NoError(t, err)
   717  
   718  		want := stores.dataPoints[:2]
   719  		wantPageInfo := entities.PageInfo{
   720  			HasNextPage:     true,
   721  			HasPreviousPage: false,
   722  			StartCursor:     want[0].Cursor().Encode(),
   723  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   724  		}
   725  		dateRange := entities.DateRange{}
   726  		testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange,
   727  			nil, nil, pagination, want, wantPageInfo)
   728  	})
   729  
   730  	t.Run("should return the first page after the cursor when first is specified with a cursor", func(t *testing.T) {
   731  		first = 2
   732  		after = stores.dataPoints[1].Cursor().Encode()
   733  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   734  		require.NoError(t, err)
   735  		want := stores.dataPoints[2:4]
   736  		wantPageInfo := entities.PageInfo{
   737  			HasNextPage:     true,
   738  			HasPreviousPage: true,
   739  			StartCursor:     want[0].Cursor().Encode(),
   740  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   741  		}
   742  		dateRange := entities.DateRange{}
   743  		testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange,
   744  			nil, nil, pagination, want, wantPageInfo)
   745  	})
   746  
   747  	t.Run("should return the last page when last is specified with no cursor", func(t *testing.T) {
   748  		last = 2
   749  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   750  		require.NoError(t, err)
   751  		want := stores.dataPoints[len(stores.dataPoints)-2:]
   752  		wantPageInfo := entities.PageInfo{
   753  			HasNextPage:     false,
   754  			HasPreviousPage: true,
   755  			StartCursor:     want[0].Cursor().Encode(),
   756  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   757  		}
   758  		dateRange := entities.DateRange{}
   759  		testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange,
   760  			nil, nil, pagination, want, wantPageInfo)
   761  	})
   762  
   763  	t.Run("should return the last page before the cursor when last is specified with a cursor", func(t *testing.T) {
   764  		last = 2
   765  		before = stores.dataPoints[3].Cursor().Encode()
   766  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   767  		require.NoError(t, err)
   768  		want := stores.dataPoints[1:3]
   769  		wantPageInfo := entities.PageInfo{
   770  			HasNextPage:     true,
   771  			HasPreviousPage: true,
   772  			StartCursor:     want[0].Cursor().Encode(),
   773  			EndCursor:       want[len(want)-1].Cursor().Encode(),
   774  		}
   775  		dateRange := entities.DateRange{}
   776  		testFundingPeriodsListDataPoints(t, ctx, stores.fp, stores.markets[0].ID, dateRange,
   777  			nil, nil, pagination, want, wantPageInfo)
   778  	})
   779  }
   780  
   781  func TestFundingPeriodDataPointSource(t *testing.T) {
   782  	var fundingPeriodDataPointSource eventspb.FundingPeriodDataPoint_Source
   783  	sources := getEnums(t, fundingPeriodDataPointSource)
   784  	assert.Equal(t, 3, len(sources))
   785  	for s, source := range sources {
   786  		t.Run(source, func(t *testing.T) {
   787  			ctx := tempTransaction(t)
   788  
   789  			stores := setupFundingPeriodTests(ctx, t)
   790  			dp := entities.FundingPeriodDataPoint{
   791  				MarketID:         stores.markets[0].ID,
   792  				FundingPeriodSeq: 1,
   793  				DataPointType:    entities.FundingPeriodDataPointSource(s),
   794  				Price:            num.DecimalFromFloat(1.0),
   795  				Twap:             num.DecimalFromFloat(1.0),
   796  				Timestamp:        stores.blocks[2].VegaTime,
   797  				VegaTime:         stores.blocks[2].VegaTime,
   798  				TxHash:           generateTxHash(),
   799  			}
   800  			require.NoError(t, stores.fp.AddDataPoint(ctx, &dp))
   801  			got := entities.FundingPeriodDataPoint{}
   802  			require.NoError(t, pgxscan.Get(ctx, stores.fp, &got,
   803  				`select * from funding_period_data_points where market_id = $1 and funding_period_seq = $2 and data_point_type = $3`,
   804  				dp.MarketID, dp.FundingPeriodSeq, dp.DataPointType),
   805  			)
   806  			assert.Equal(t, dp, got)
   807  		})
   808  	}
   809  }