code.vegaprotocol.io/vega@v0.79.0/core/products/perpetual_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 products_test
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"math"
    23  	"sort"
    24  	"testing"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/core/datasource"
    28  	dscommon "code.vegaprotocol.io/vega/core/datasource/common"
    29  	dstypes "code.vegaprotocol.io/vega/core/datasource/common"
    30  	"code.vegaprotocol.io/vega/core/datasource/external/signedoracle"
    31  	"code.vegaprotocol.io/vega/core/datasource/spec"
    32  	"code.vegaprotocol.io/vega/core/events"
    33  	"code.vegaprotocol.io/vega/core/products"
    34  	"code.vegaprotocol.io/vega/core/products/mocks"
    35  	"code.vegaprotocol.io/vega/core/types"
    36  	tmocks "code.vegaprotocol.io/vega/core/vegatime/mocks"
    37  	"code.vegaprotocol.io/vega/libs/num"
    38  	"code.vegaprotocol.io/vega/libs/ptr"
    39  	"code.vegaprotocol.io/vega/logging"
    40  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    41  
    42  	"github.com/golang/mock/gomock"
    43  	"github.com/stretchr/testify/assert"
    44  	"github.com/stretchr/testify/require"
    45  )
    46  
    47  func TestPeriodicSettlement(t *testing.T) {
    48  	t.Run("twap calculations after leaving opening auction", testTWAPAfterOpeningAuction)
    49  	t.Run("period end with no data point", testPeriodEndWithNoDataPoints)
    50  	t.Run("equal internal and external prices", testEqualInternalAndExternalPrices)
    51  	t.Run("constant difference long pays short", testConstantDifferenceLongPaysShort)
    52  	t.Run("data points outside of period", testDataPointsOutsidePeriod)
    53  	t.Run("data points not on boundary", testDataPointsNotOnBoundary)
    54  	t.Run("matching data points outside of period through callbacks", testRegisteredCallbacks)
    55  	t.Run("non-matching data points outside of period through callbacks", testRegisteredCallbacksWithDifferentData)
    56  	t.Run("funding payments with interest rate", testFundingPaymentsWithInterestRate)
    57  	t.Run("funding payments with interest rate clamped", testFundingPaymentsWithInterestRateClamped)
    58  	t.Run("terminate perps market test", testTerminateTrading)
    59  	t.Run("margin increase", testGetMarginIncrease)
    60  	t.Run("margin increase, negative payment", testGetMarginIncreaseNegativePayment)
    61  	t.Run("test pathological case with out of order points", testOutOfOrderPointsBeforePeriodStart)
    62  	t.Run("test update perpetual", testUpdatePerpetual)
    63  	t.Run("test terminate trading coincides with time trigger", testTerminateTradingCoincidesTimeTrigger)
    64  	t.Run("test funding-payment on start boundary", testFundingPaymentOnStartBoundary)
    65  	t.Run("test data point is before the first point", TestPrependPoint)
    66  }
    67  
    68  func TestRealData(t *testing.T) {
    69  	tcs := []struct {
    70  		name    string
    71  		reverse bool
    72  	}{
    73  		{
    74  			"in order",
    75  			false,
    76  		},
    77  		{
    78  			"out of order",
    79  			false,
    80  		},
    81  	}
    82  
    83  	for _, tc := range tcs {
    84  		t.Run(tc.name, func(tt *testing.T) {
    85  			perp := testPerpetual(t)
    86  			defer perp.ctrl.Finish()
    87  
    88  			ctx := context.Background()
    89  			tstData, err := getGQLData()
    90  			require.NoError(t, err)
    91  			data := tstData.GetDataPoints(false)
    92  
    93  			// want to start the period from before the point with the smallest time
    94  			seq := math.MaxInt
    95  			st := data[0].t
    96  			nd := data[0].t
    97  			for i := 0; i < len(data); i++ {
    98  				if data[i].t < st {
    99  					st = data[i].t
   100  				}
   101  				if data[i].t > nd {
   102  					nd = data[i].t
   103  				}
   104  				seq = num.MinV(seq, data[i].seq)
   105  			}
   106  
   107  			perp.perpetual.SetSettlementListener(func(context.Context, *num.Numeric) {})
   108  			// leave opening auction
   109  			whenLeaveOpeningAuction(t, perp, st-1)
   110  
   111  			perp.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   112  			perp.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   113  
   114  			// set the first internal data-point
   115  			for _, dp := range data {
   116  				if dp.seq > seq {
   117  					perp.perpetual.PromptSettlementCue(ctx, dp.t)
   118  					seq = dp.seq
   119  				}
   120  				perp.perpetual.AddTestExternalPoint(ctx, dp.price, dp.t)
   121  				perp.perpetual.SubmitDataPoint(ctx, num.UintZero().Add(dp.price, num.NewUint(100)), dp.t)
   122  			}
   123  			d := perp.perpetual.GetData(nd).Data.(*types.PerpetualData)
   124  			assert.Equal(t, "29124220000", d.ExternalTWAP)
   125  			assert.Equal(t, "29124220100", d.InternalTWAP)
   126  			assert.Equal(t, "100", d.FundingPayment)
   127  		})
   128  	}
   129  }
   130  
   131  func testTWAPAfterOpeningAuction(t *testing.T) {
   132  	perp := testPerpetual(t)
   133  	defer perp.ctrl.Finish()
   134  
   135  	ctx := context.Background()
   136  
   137  	now := time.Unix(2000, 0).UnixNano()
   138  
   139  	// no error because its really a callback from the oracle engine, but we expect no events
   140  	perp.perpetual.AddTestExternalPoint(ctx, num.UintOne(), now)
   141  	data := perp.perpetual.GetData(2000)
   142  	require.Nil(t, data)
   143  
   144  	// internal data point recevied without error
   145  	perp.broker.EXPECT().Send(gomock.Any()).Times(1)
   146  	err := perp.perpetual.SubmitDataPoint(ctx, num.NewUint(100000), now)
   147  	data = perp.perpetual.GetData(2000)
   148  	assert.NoError(t, err)
   149  	require.Nil(t, data)
   150  
   151  	// check that settlement cues are ignored, we expect no events when it is
   152  	perp.perpetual.PromptSettlementCue(ctx, 4000)
   153  
   154  	// now leaving opening auction
   155  	perp.broker.EXPECT().Send(gomock.Any()).Times(1)
   156  	perp.ts.EXPECT().GetTimeNow().Times(1).Return(time.Unix(2000, 0))
   157  	perp.perpetual.UpdateAuctionState(ctx, false)
   158  
   159  	// send in an external data-point which actually hits before opening auction time because it took a while to wobble through
   160  	perp.broker.EXPECT().Send(gomock.Any()).Times(1)
   161  	perp.perpetual.AddTestExternalPoint(ctx, num.NewUint(100000), now-int64(time.Minute))
   162  	fundingPayment := getFundingPayment(t, perp, now)
   163  	require.Equal(t, "0", fundingPayment)
   164  
   165  	// now another data point but both at the same time
   166  	dp := &testDataPoint{price: num.NewUint(200000), t: now + int64(time.Second)}
   167  	submitPointWithDifference(t, perp, dp, 0)
   168  	fundingPayment = getFundingPayment(t, perp, now+int64(time.Minute))
   169  	require.Equal(t, "0", fundingPayment)
   170  }
   171  
   172  func testPeriodEndWithNoDataPoints(t *testing.T) {
   173  	perp := testPerpetual(t)
   174  	defer perp.ctrl.Finish()
   175  
   176  	ctx := context.Background()
   177  	now := time.Unix(1, 0)
   178  
   179  	// funding payment will be zero because there are no data points
   180  	var called bool
   181  	fn := func(context.Context, *num.Numeric) {
   182  		called = true
   183  	}
   184  	perp.perpetual.SetSettlementListener(fn)
   185  
   186  	whenLeaveOpeningAuction(t, perp, now.UnixNano())
   187  
   188  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   189  	perp.perpetual.PromptSettlementCue(ctx, now.Add(40*time.Second).UnixNano())
   190  
   191  	// we had no points to check we didn't call into listener
   192  	assert.False(t, called)
   193  }
   194  
   195  func TestPrependPoint(t *testing.T) {
   196  	perp := testPerpetual(t)
   197  	defer perp.ctrl.Finish()
   198  
   199  	ctx := context.Background()
   200  	now := time.Unix(1000, 0)
   201  	whenLeaveOpeningAuction(t, perp, now.UnixNano())
   202  
   203  	perp.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   204  
   205  	// we'll use this point to check that we do not lose a later point when we recalc when earlier points come in
   206  	err := perp.perpetual.SubmitDataPoint(ctx, num.NewUint(10), time.Unix(5000, 0).UnixNano())
   207  	perp.perpetual.AddTestExternalPoint(ctx, num.NewUint(9), time.Unix(5000, 0).UnixNano())
   208  	require.NoError(t, err)
   209  	require.Equal(t, "1", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   210  
   211  	// first point is after the start of the period
   212  	err = perp.perpetual.SubmitDataPoint(ctx, num.NewUint(10), time.Unix(2000, 0).UnixNano())
   213  	require.NoError(t, err)
   214  	require.Equal(t, "1", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   215  
   216  	// now another one comes in before this, but also after the start of the period
   217  	err = perp.perpetual.SubmitDataPoint(ctx, num.NewUint(50), time.Unix(1500, 0).UnixNano())
   218  	require.NoError(t, err)
   219  	require.Equal(t, "6", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   220  
   221  	// now one comes in before the start of the period
   222  	err = perp.perpetual.SubmitDataPoint(ctx, num.NewUint(50), time.Unix(500, 0).UnixNano())
   223  	require.NoError(t, err)
   224  	require.Equal(t, "11", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   225  
   226  	// now one comes in before this point
   227  	err = perp.perpetual.SubmitDataPoint(ctx, num.UintOne(), time.Unix(250, 0).UnixNano())
   228  	require.ErrorIs(t, err, products.ErrDataPointIsTooOld)
   229  	require.Equal(t, "11", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   230  
   231  	// now one comes in after the first point, but before the period start
   232  	err = perp.perpetual.SubmitDataPoint(ctx, num.UintOne(), time.Unix(500, 0).UnixNano())
   233  	require.ErrorIs(t, err, products.ErrDataPointAlreadyExistsAtTime)
   234  	require.Equal(t, "11", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   235  
   236  	// now one comes in after the first point, but before the period start
   237  	err = perp.perpetual.SubmitDataPoint(ctx, num.NewUint(50), time.Unix(750, 0).UnixNano())
   238  	require.NoError(t, err)
   239  	require.Equal(t, "11", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   240  
   241  	// now one comes that equals period start
   242  	err = perp.perpetual.SubmitDataPoint(ctx, num.NewUint(50), time.Unix(1000, 0).UnixNano())
   243  	require.NoError(t, err)
   244  	require.Equal(t, "11", getFundingPayment(t, perp, time.Unix(5000, 0).UnixNano()))
   245  
   246  	err = perp.perpetual.SubmitDataPoint(ctx, num.NewUint(100000), time.Unix(750, 0).UnixNano())
   247  	require.ErrorIs(t, err, products.ErrDataPointIsTooOld)
   248  }
   249  
   250  func testEqualInternalAndExternalPrices(t *testing.T) {
   251  	perp := testPerpetual(t)
   252  	defer perp.ctrl.Finish()
   253  	ctx := context.Background()
   254  
   255  	// set of the data points such that difference in averages is 0
   256  	points := getTestDataPoints(t)
   257  
   258  	// tell the perpetual that we are ready to accept settlement stuff
   259  	whenLeaveOpeningAuction(t, perp, points[0].t)
   260  
   261  	// send in some data points
   262  	perp.broker.EXPECT().Send(gomock.Any()).Times(len(points) * 2)
   263  	for _, p := range points {
   264  		// send in an external and a matching internal
   265  		require.NoError(t, perp.perpetual.SubmitDataPoint(ctx, p.price, p.t))
   266  		perp.perpetual.AddTestExternalPoint(ctx, p.price, p.t)
   267  	}
   268  
   269  	// ask for the funding payment
   270  	var fundingPayment *num.Numeric
   271  	fn := func(_ context.Context, fp *num.Numeric) {
   272  		fundingPayment = fp
   273  	}
   274  	perp.perpetual.SetSettlementListener(fn)
   275  
   276  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   277  	perp.broker.EXPECT().SendBatch(gomock.Any()).Times(1)
   278  	perp.perpetual.PromptSettlementCue(ctx, points[len(points)-1].t)
   279  	assert.NotNil(t, fundingPayment)
   280  	assert.True(t, fundingPayment.IsInt())
   281  	assert.Equal(t, "0", fundingPayment.String())
   282  }
   283  
   284  func testConstantDifferenceLongPaysShort(t *testing.T) {
   285  	perp := testPerpetual(t)
   286  	defer perp.ctrl.Finish()
   287  	ctx := context.Background()
   288  
   289  	// test data
   290  	points := getTestDataPoints(t)
   291  
   292  	// when: the funding period starts at 1000
   293  	whenLeaveOpeningAuction(t, perp, points[0].t)
   294  
   295  	// and: the difference in external/internal prices are a constant -10
   296  	submitDataWithDifference(t, perp, points, -10)
   297  
   298  	// funding payment will be zero so no transfers
   299  	var fundingPayment *num.Numeric
   300  	fn := func(_ context.Context, fp *num.Numeric) {
   301  		fundingPayment = fp
   302  	}
   303  	perp.perpetual.SetSettlementListener(fn)
   304  
   305  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   306  	perp.broker.EXPECT().SendBatch(gomock.Any()).Times(1)
   307  
   308  	productData := perp.perpetual.GetData(points[len(points)-1].t)
   309  	perpData, ok := productData.Data.(*types.PerpetualData)
   310  	assert.True(t, ok)
   311  
   312  	perp.perpetual.PromptSettlementCue(ctx, points[len(points)-1].t)
   313  	assert.NotNil(t, fundingPayment)
   314  	assert.True(t, fundingPayment.IsInt())
   315  	assert.Equal(t, "-10", fundingPayment.String())
   316  	assert.Equal(t, "-10", perpData.FundingPayment)
   317  	assert.Equal(t, "116", perpData.ExternalTWAP)
   318  	assert.Equal(t, "106", perpData.InternalTWAP)
   319  	assert.Equal(t, "-0.0862068965517241", perpData.FundingRate)
   320  	assert.Equal(t, uint64(0), perpData.SeqNum)
   321  	assert.Equal(t, int64(3600000000000), perpData.StartTime)
   322  }
   323  
   324  func testDataPointsOutsidePeriod(t *testing.T) {
   325  	perp := testPerpetual(t)
   326  	defer perp.ctrl.Finish()
   327  	ctx := context.Background()
   328  
   329  	// set of the data points such that difference in averages is 0
   330  	points := getTestDataPoints(t)
   331  
   332  	// tell the perpetual that we are ready to accept settlement stuff
   333  	whenLeaveOpeningAuction(t, perp, points[0].t)
   334  
   335  	// add data-points from the past, they will just be ignored
   336  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   337  	require.NoError(t, perp.perpetual.SubmitDataPoint(ctx, num.UintOne(), points[0].t-int64(time.Hour)))
   338  	perp.perpetual.AddTestExternalPoint(ctx, num.UintZero(), points[0].t-int64(time.Hour))
   339  
   340  	// send in some data points
   341  	perp.broker.EXPECT().Send(gomock.Any()).Times(len(points) * 2)
   342  	for _, p := range points {
   343  		// send in an external and a matching internal
   344  		require.NoError(t, perp.perpetual.SubmitDataPoint(ctx, p.price, p.t))
   345  		perp.perpetual.AddTestExternalPoint(ctx, p.price, p.t)
   346  	}
   347  
   348  	// add some data-points in the future from when we will cue the end of the funding period
   349  	// they should not affect the funding payment of this period
   350  	lastPoint := points[len(points)-1]
   351  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   352  	require.NoError(t, perp.perpetual.SubmitDataPoint(ctx, num.UintOne(), lastPoint.t+int64(time.Hour)))
   353  	perp.perpetual.AddTestExternalPoint(ctx, num.UintZero(), lastPoint.t+int64(time.Hour))
   354  
   355  	// ask for the funding payment
   356  	var fundingPayment *num.Numeric
   357  	fn := func(_ context.Context, fp *num.Numeric) {
   358  		fundingPayment = fp
   359  	}
   360  	perp.perpetual.SetSettlementListener(fn)
   361  
   362  	// 6 times because: end + start of the period, plus 2 carry over points for external + internal (total 4)
   363  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   364  	perp.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) {
   365  		require.Equal(t, 4, len(evts)) // 4 carry over points
   366  	})
   367  	perp.perpetual.PromptSettlementCue(ctx, lastPoint.t)
   368  	assert.NotNil(t, fundingPayment)
   369  	assert.True(t, fundingPayment.IsInt())
   370  	assert.Equal(t, "0", fundingPayment.String())
   371  }
   372  
   373  func testDataPointsNotOnBoundary(t *testing.T) {
   374  	perp := testPerpetual(t)
   375  	defer perp.ctrl.Finish()
   376  	ctx := context.Background()
   377  
   378  	// set of the data points such that difference in averages is 0
   379  	points := getTestDataPoints(t)
   380  
   381  	// start time is *after* our first data points
   382  	whenLeaveOpeningAuction(t, perp, points[0].t+int64(time.Second))
   383  
   384  	// send in some data points
   385  	submitDataWithDifference(t, perp, points, 10)
   386  
   387  	// ask for the funding payment
   388  	var fundingPayment *num.Numeric
   389  	fn := func(_ context.Context, fp *num.Numeric) {
   390  		fundingPayment = fp
   391  	}
   392  	perp.perpetual.SetSettlementListener(fn)
   393  
   394  	// period end is *after* our last point
   395  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   396  	perp.broker.EXPECT().SendBatch(gomock.Any()).Times(1)
   397  	perp.perpetual.PromptSettlementCue(ctx, points[len(points)-1].t+int64(time.Hour))
   398  	assert.NotNil(t, fundingPayment)
   399  	assert.True(t, fundingPayment.IsInt())
   400  	assert.Equal(t, "10", fundingPayment.String())
   401  }
   402  
   403  func testOutOfOrderPointsBeforePeriodStart(t *testing.T) {
   404  	perp := testPerpetual(t)
   405  	defer perp.ctrl.Finish()
   406  	ctx := context.Background()
   407  
   408  	// start time will be after the *second* data point
   409  	whenLeaveOpeningAuction(t, perp, 1693398617000000000)
   410  
   411  	price := num.NewUint(100000000)
   412  	timestamps := []int64{
   413  		1693398614000000000,
   414  		1693398615000000000,
   415  		1693398616000000000,
   416  		1693398618000000000,
   417  		1693398617000000000,
   418  	}
   419  
   420  	perp.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   421  	perp.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   422  	for _, tt := range timestamps {
   423  		perp.perpetual.AddTestExternalPoint(ctx, price, tt)
   424  		perp.perpetual.SubmitDataPoint(ctx, num.UintZero().Add(price, num.NewUint(100000000)), tt)
   425  	}
   426  
   427  	// ask for the funding payment
   428  	var fundingPayment *num.Numeric
   429  	fn := func(_ context.Context, fp *num.Numeric) {
   430  		fundingPayment = fp
   431  	}
   432  	perp.perpetual.SetSettlementListener(fn)
   433  
   434  	// period end is *after* our last point
   435  	perp.perpetual.PromptSettlementCue(ctx, 1693398617000000000+int64(time.Hour))
   436  	assert.NotNil(t, fundingPayment)
   437  	assert.True(t, fundingPayment.IsInt())
   438  	assert.Equal(t, "100000000", fundingPayment.String())
   439  }
   440  
   441  func testRegisteredCallbacks(t *testing.T) {
   442  	log := logging.NewTestLogger()
   443  	ctrl := gomock.NewController(t)
   444  	oe := mocks.NewMockOracleEngine(ctrl)
   445  	broker := mocks.NewMockBroker(ctrl)
   446  	ts := tmocks.NewMockTimeService(ctrl)
   447  	exp := &num.Numeric{}
   448  	exp.SetUint(num.UintZero())
   449  	ctx := context.Background()
   450  	received := false
   451  	points := getTestDataPoints(t)
   452  	marketSettle := func(_ context.Context, data *num.Numeric) {
   453  		received = true
   454  		require.Equal(t, exp.String(), data.String())
   455  	}
   456  	var settle, period spec.OnMatchedData
   457  	oe.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).DoAndReturn(func(_ context.Context, s spec.Spec, cb spec.OnMatchedData) (spec.SubscriptionID, spec.Unsubscriber, error) {
   458  		filters := s.OriginalSpec.GetDefinition().DataSourceType.GetFilters()
   459  		for _, f := range filters {
   460  			if f.Key.Type == datapb.PropertyKey_TYPE_INTEGER || f.Key.Type == datapb.PropertyKey_TYPE_DECIMAL {
   461  				settle = cb
   462  				return spec.SubscriptionID(1), func(_ context.Context, _ spec.SubscriptionID) {}, nil
   463  			}
   464  		}
   465  		period = cb
   466  		return spec.SubscriptionID(1), func(_ context.Context, _ spec.SubscriptionID) {}, nil
   467  	})
   468  	broker.EXPECT().Send(gomock.Any()).AnyTimes()
   469  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   470  	perp := getTestPerpProd(t)
   471  	perpetual, err := products.NewPerpetual(context.Background(), log, perp, "", ts, oe, broker, 1)
   472  	require.NoError(t, err)
   473  	require.NotNil(t, settle)
   474  	require.NotNil(t, period)
   475  	// register the callback
   476  	perpetual.NotifyOnSettlementData(marketSettle)
   477  	perpetual.NotifyOnDataSourcePropagation(func(context.Context, *num.Uint) {})
   478  
   479  	ts.EXPECT().GetTimeNow().Times(1).Return(time.Unix(0, points[0].t))
   480  	perpetual.UpdateAuctionState(ctx, false)
   481  
   482  	for _, p := range points {
   483  		// send in an external and a matching internal
   484  		require.NoError(t, perpetual.SubmitDataPoint(ctx, p.price, p.t))
   485  		settle(ctx, dscommon.Data{
   486  			Data: map[string]string{
   487  				perp.DataSourceSpecBinding.SettlementDataProperty: p.price.String(),
   488  			},
   489  			MetaData: map[string]string{
   490  				"eth-block-time": fmt.Sprintf("%d", time.Unix(0, p.t).Unix()),
   491  			},
   492  		})
   493  	}
   494  	// add some data-points in the future from when we will cue the end of the funding period
   495  	// they should not affect the funding payment of this period
   496  	lastPoint := points[len(points)-1]
   497  	require.NoError(t, perpetual.SubmitDataPoint(ctx, num.UintOne(), lastPoint.t+int64(time.Hour)))
   498  	settle(ctx, dscommon.Data{
   499  		Data: map[string]string{
   500  			perp.DataSourceSpecBinding.SettlementDataProperty: "1",
   501  		},
   502  		MetaData: map[string]string{
   503  			"eth-block-time": fmt.Sprintf("%d", time.Unix(0, lastPoint.t+int64(time.Hour)).Unix()),
   504  		},
   505  	})
   506  	// make sure the data-point outside of the period doesn't trigger the schedule callback
   507  	// that has to come from the oracle, too
   508  	assert.False(t, received)
   509  
   510  	// end period
   511  	period(ctx, dscommon.Data{
   512  		Data: map[string]string{
   513  			perp.DataSourceSpecBinding.SettlementScheduleProperty: fmt.Sprintf("%d", time.Unix(0, lastPoint.t).Unix()),
   514  		},
   515  	})
   516  
   517  	assert.True(t, received)
   518  }
   519  
   520  func testRegisteredCallbacksWithDifferentData(t *testing.T) {
   521  	log := logging.NewTestLogger()
   522  	ctrl := gomock.NewController(t)
   523  	oe := mocks.NewMockOracleEngine(ctrl)
   524  	broker := mocks.NewMockBroker(ctrl)
   525  	ts := tmocks.NewMockTimeService(ctrl)
   526  	exp := &num.Numeric{}
   527  	// should be 2
   528  	res, _ := num.IntFromString("-4", 10)
   529  	exp.SetInt(res)
   530  	ctx := context.Background()
   531  	received := false
   532  	points := getTestDataPoints(t)
   533  	marketSettle := func(_ context.Context, data *num.Numeric) {
   534  		received = true
   535  		require.Equal(t, exp.String(), data.String())
   536  	}
   537  	var settle, period spec.OnMatchedData
   538  	oe.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).DoAndReturn(func(_ context.Context, s spec.Spec, cb spec.OnMatchedData) (spec.SubscriptionID, spec.Unsubscriber, error) {
   539  		filters := s.OriginalSpec.GetDefinition().DataSourceType.GetFilters()
   540  		for _, f := range filters {
   541  			if f.Key.Type == datapb.PropertyKey_TYPE_INTEGER || f.Key.Type == datapb.PropertyKey_TYPE_DECIMAL {
   542  				settle = cb
   543  				return spec.SubscriptionID(1), func(_ context.Context, _ spec.SubscriptionID) {}, nil
   544  			}
   545  		}
   546  		period = cb
   547  		return spec.SubscriptionID(1), func(_ context.Context, _ spec.SubscriptionID) {}, nil
   548  	})
   549  	broker.EXPECT().Send(gomock.Any()).AnyTimes()
   550  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   551  	perp := getTestPerpProd(t)
   552  	perpetual, err := products.NewPerpetual(context.Background(), log, perp, "", ts, oe, broker, 1)
   553  	require.NoError(t, err)
   554  	require.NotNil(t, settle)
   555  	require.NotNil(t, period)
   556  	// register the callback
   557  	perpetual.NotifyOnSettlementData(marketSettle)
   558  	perpetual.NotifyOnDataSourcePropagation(func(context.Context, *num.Uint) {})
   559  
   560  	// start the funding period
   561  	ts.EXPECT().GetTimeNow().Times(1).Return(time.Unix(0, points[0].t))
   562  	perpetual.UpdateAuctionState(ctx, false)
   563  
   564  	// send data in from before the start of the period, it should not affect the result
   565  	require.NoError(t, perpetual.SubmitDataPoint(ctx, num.UintOne(), points[0].t-int64(time.Hour)))
   566  	// callback to receive settlement data
   567  	settle(ctx, dscommon.Data{
   568  		Data: map[string]string{
   569  			perp.DataSourceSpecBinding.SettlementDataProperty: "1",
   570  		},
   571  		MetaData: map[string]string{
   572  			"eth-block-time": fmt.Sprintf("%d", time.Unix(0, points[0].t-int64(time.Hour)).Unix()),
   573  		},
   574  	})
   575  
   576  	// send all external points, but not all internal ones and have their price
   577  	// be one less. This means external twap > internal tswap so we expect a negative funding rate
   578  	for i, p := range points {
   579  		if i%2 == 0 {
   580  			ip := num.UintZero().Sub(p.price, num.UintOne())
   581  			require.NoError(t, perpetual.SubmitDataPoint(ctx, ip, p.t))
   582  		}
   583  		settle(ctx, dscommon.Data{
   584  			Data: map[string]string{
   585  				perp.DataSourceSpecBinding.SettlementDataProperty: p.price.String(),
   586  			},
   587  			MetaData: map[string]string{
   588  				"eth-block-time": fmt.Sprintf("%d", time.Unix(0, p.t).Unix()),
   589  			},
   590  		})
   591  	}
   592  
   593  	// add some data-points in the future from when we will cue the end of the funding period
   594  	// they should not affect the funding payment of this period
   595  	lastPoint := points[len(points)-1]
   596  	require.NoError(t, perpetual.SubmitDataPoint(ctx, num.UintOne(), lastPoint.t+int64(time.Hour)))
   597  	settle(ctx, dscommon.Data{
   598  		Data: map[string]string{
   599  			perp.DataSourceSpecBinding.SettlementDataProperty: "1",
   600  		},
   601  		MetaData: map[string]string{
   602  			"eth-block-time": fmt.Sprintf("%d", time.Unix(0, lastPoint.t+int64(time.Hour)).Unix()),
   603  		},
   604  	})
   605  
   606  	// end period
   607  	period(ctx, dscommon.Data{
   608  		Data: map[string]string{
   609  			perp.DataSourceSpecBinding.SettlementScheduleProperty: fmt.Sprintf("%d", time.Unix(0, lastPoint.t).Unix()),
   610  		},
   611  	})
   612  
   613  	assert.True(t, received)
   614  }
   615  
   616  func testFundingPaymentsWithInterestRate(t *testing.T) {
   617  	perp := testPerpetual(t)
   618  	perp.perp.InterestRate = num.DecimalFromFloat(0.01)
   619  	perp.perp.ClampLowerBound = num.DecimalFromInt64(-1)
   620  	perp.perp.ClampUpperBound = num.DecimalFromInt64(1)
   621  
   622  	defer perp.ctrl.Finish()
   623  	ctx := context.Background()
   624  
   625  	// test data
   626  	points := getTestDataPoints(t)
   627  	lastPoint := points[len(points)-1]
   628  
   629  	// when: the funding period starts
   630  	whenLeaveOpeningAuction(t, perp, points[0].t)
   631  
   632  	// scale the price so that we have more precision to work with
   633  	scale := num.UintFromUint64(100000000000)
   634  	for _, p := range points {
   635  		p.price = num.UintZero().Mul(p.price, scale)
   636  	}
   637  
   638  	// and: the difference in external/internal prices are a constant -10
   639  	submitDataWithDifference(t, perp, points, -1000000000000)
   640  
   641  	// Whats happening:
   642  	// the fundingPayment without the interest terms will be -10000000000000
   643  	//
   644  	// interest will be (1 + r * t) * swap - fswap
   645  	// where r = 0.01, t = 0.25, stwap = 11666666666666, ftwap = 10666666666656,
   646  	// interest = (1 + 0.0025) * 11666666666666 - 10666666666656 = 1029166666666
   647  
   648  	// since lower clamp <    interest   < upper clamp
   649  	//   -11666666666666 < 1029166666666 < 11666666666666
   650  	// there is no adjustment and so
   651  	// funding payment = -10000000000000 + 1029166666666 = 29166666666
   652  
   653  	var fundingPayment *num.Numeric
   654  	fn := func(_ context.Context, fp *num.Numeric) {
   655  		fundingPayment = fp
   656  	}
   657  	perp.perpetual.SetSettlementListener(fn)
   658  
   659  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   660  	perp.broker.EXPECT().SendBatch(gomock.Any()).Times(1)
   661  	perp.perpetual.PromptSettlementCue(ctx, lastPoint.t)
   662  	assert.NotNil(t, fundingPayment)
   663  	assert.True(t, fundingPayment.IsInt())
   664  	assert.Equal(t, "29166666666", fundingPayment.String())
   665  }
   666  
   667  func testFundingPaymentsWithInterestRateClamped(t *testing.T) {
   668  	perp := testPerpetual(t)
   669  	defer perp.ctrl.Finish()
   670  	perp.perp.InterestRate = num.DecimalFromFloat(0.5)
   671  	perp.perp.ClampLowerBound = num.DecimalFromFloat(0.001)
   672  	perp.perp.ClampUpperBound = num.DecimalFromFloat(0.002)
   673  	ctx := context.Background()
   674  
   675  	// test data
   676  	points := getTestDataPoints(t)
   677  
   678  	// when: the funding period starts
   679  	whenLeaveOpeningAuction(t, perp, points[0].t)
   680  
   681  	// scale the price so that we have more precision to work with
   682  	scale := num.UintFromUint64(100000000000)
   683  	for _, p := range points {
   684  		p.price = num.UintZero().Mul(p.price, scale)
   685  	}
   686  
   687  	// and: the difference in external/internal prices are a constant -10
   688  	submitDataWithDifference(t, perp, points, -10)
   689  
   690  	// Whats happening:
   691  	// the fundingPayment will be -10 without the interest terms
   692  	//
   693  	// interest will be (1 + r * t) * swap - fswap
   694  	// where stwap=116, ftwap=106, r=0.5 t=0.25
   695  	// interest = (1 + 0.125) * 11666666666666 - 11666666666656 = 1458333333343
   696  
   697  	// if we consider the clamps:
   698  	// lower clamp:   11666666666
   699  	// interest:    1458333333343
   700  	// upper clamp:   23333333333
   701  
   702  	// so we have exceeded the upper clamp the the interest term is snapped to it and so:
   703  	// funding payment = -10 + 23333333333 = 23333333323
   704  
   705  	var fundingPayment *num.Numeric
   706  	fn := func(_ context.Context, fp *num.Numeric) {
   707  		fundingPayment = fp
   708  	}
   709  	perp.perpetual.SetSettlementListener(fn)
   710  
   711  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   712  	perp.broker.EXPECT().SendBatch(gomock.Any()).Times(1)
   713  	perp.perpetual.PromptSettlementCue(ctx, points[3].t)
   714  	assert.NotNil(t, fundingPayment)
   715  	assert.True(t, fundingPayment.IsInt())
   716  	assert.Equal(t, "23333333323", fundingPayment.String())
   717  }
   718  
   719  func testTerminateTrading(t *testing.T) {
   720  	perp := testPerpetual(t)
   721  	defer perp.ctrl.Finish()
   722  	ctx := context.Background()
   723  
   724  	// set of the data points such that difference in averages is 0
   725  	points := getTestDataPoints(t)
   726  
   727  	// tell the perpetual that we are ready to accept settlement stuff
   728  	whenLeaveOpeningAuction(t, perp, points[0].t)
   729  
   730  	// send in some data points
   731  	perp.broker.EXPECT().Send(gomock.Any()).Times(len(points) * 2)
   732  	for _, p := range points {
   733  		// send in an external and a matching internal
   734  		require.NoError(t, perp.perpetual.SubmitDataPoint(ctx, p.price, p.t))
   735  		perp.perpetual.AddTestExternalPoint(ctx, p.price, p.t)
   736  	}
   737  
   738  	// ask for the funding payment
   739  	var fundingPayment *num.Numeric
   740  	fn := func(_ context.Context, fp *num.Numeric) {
   741  		fundingPayment = fp
   742  	}
   743  	perp.perpetual.SetSettlementListener(fn)
   744  
   745  	perp.ts.EXPECT().GetTimeNow().Times(1).Return(time.Unix(10, points[len(points)-1].t))
   746  	perp.broker.EXPECT().Send(gomock.Any()).Times(1)
   747  	perp.perpetual.UnsubscribeTradingTerminated(ctx)
   748  	assert.NotNil(t, fundingPayment)
   749  	assert.True(t, fundingPayment.IsInt())
   750  	assert.Equal(t, "0", fundingPayment.String())
   751  }
   752  
   753  func testTerminateTradingCoincidesTimeTrigger(t *testing.T) {
   754  	perp := testPerpetual(t)
   755  	defer perp.ctrl.Finish()
   756  	ctx := context.Background()
   757  
   758  	// set of the data points such that difference in averages is 0
   759  	points := getTestDataPoints(t)
   760  
   761  	// tell the perpetual that we are ready to accept settlement stuff
   762  	whenLeaveOpeningAuction(t, perp, points[0].t)
   763  
   764  	// send in some data points
   765  	perp.broker.EXPECT().Send(gomock.Any()).Times(len(points) * 2)
   766  	for _, p := range points {
   767  		// send in an external and a matching internal
   768  		require.NoError(t, perp.perpetual.SubmitDataPoint(ctx, p.price, p.t))
   769  		perp.perpetual.AddTestExternalPoint(ctx, p.price, p.t)
   770  	}
   771  
   772  	// ask for the funding payment
   773  	var fundingPayment *num.Numeric
   774  	fn := func(_ context.Context, fp *num.Numeric) {
   775  		fundingPayment = fp
   776  	}
   777  	perp.perpetual.SetSettlementListener(fn)
   778  
   779  	// do a normal settlement cue end time
   780  	endTime := time.Unix(10, points[len(points)-1].t).Truncate(time.Second)
   781  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   782  	perp.broker.EXPECT().SendBatch(gomock.Any()).Times(1)
   783  	perp.perpetual.PromptSettlementCue(ctx, endTime.UnixNano())
   784  	assert.NotNil(t, fundingPayment)
   785  	assert.True(t, fundingPayment.IsInt())
   786  	assert.Equal(t, "0", fundingPayment.String())
   787  
   788  	// now terminate the market at the same time, we expect no funding payment, and just an event
   789  	// to say the period has ended, with no start period.
   790  	fundingPayment = nil
   791  	perp.ts.EXPECT().GetTimeNow().Times(1).Return(time.Unix(10, points[len(points)-1].t))
   792  	perp.broker.EXPECT().Send(gomock.Any()).Times(1)
   793  	perp.perpetual.UnsubscribeTradingTerminated(ctx)
   794  	assert.Nil(t, fundingPayment)
   795  }
   796  
   797  func testGetMarginIncrease(t *testing.T) {
   798  	perp := testPerpetual(t)
   799  	defer perp.ctrl.Finish()
   800  	perp.perp.MarginFundingFactor = num.DecimalFromFloat(0.5)
   801  
   802  	// test data
   803  	points := getTestDataPoints(t)
   804  
   805  	// before we've started the first funding interval margin increase is 0
   806  	inc := perp.perpetual.GetMarginIncrease(points[0].t)
   807  	assert.Equal(t, "0", inc.String())
   808  
   809  	// start funding period
   810  	whenLeaveOpeningAuction(t, perp, points[0].t)
   811  
   812  	// started interval, but not points, margin increase is 0
   813  	inc = perp.perpetual.GetMarginIncrease(points[0].t)
   814  	assert.Equal(t, "0", inc.String())
   815  
   816  	// and: the difference in external/internal prices are is 10
   817  	submitDataWithDifference(t, perp, points, 10)
   818  
   819  	lastPoint := points[len(points)-1]
   820  	inc = perp.perpetual.GetMarginIncrease(lastPoint.t)
   821  	// margin increase is margin_factor * funding-payment = 0.5 * 10
   822  	assert.Equal(t, "5", inc.String())
   823  }
   824  
   825  func testGetMarginIncreaseNegativePayment(t *testing.T) {
   826  	perp := testPerpetual(t)
   827  	defer perp.ctrl.Finish()
   828  	perp.perp.MarginFundingFactor = num.DecimalFromFloat(0.5)
   829  
   830  	// test data
   831  	points := getTestDataPoints(t)
   832  
   833  	// start funding period
   834  	whenLeaveOpeningAuction(t, perp, points[0].t)
   835  
   836  	// and: the difference in external/internal prices are is 10
   837  	submitDataWithDifference(t, perp, points, -10)
   838  
   839  	lastPoint := points[len(points)-1]
   840  	inc := perp.perpetual.GetMarginIncrease(lastPoint.t)
   841  	// margin increase is margin_factor * funding-payment = 0.5 * 10
   842  	assert.Equal(t, "-5", inc.String())
   843  }
   844  
   845  func testUpdatePerpetual(t *testing.T) {
   846  	perp := testPerpetual(t)
   847  	defer perp.ctrl.Finish()
   848  	perp.perp.MarginFundingFactor = num.DecimalFromFloat(0.5)
   849  	ctx := context.Background()
   850  
   851  	// test data
   852  	points := getTestDataPoints(t)
   853  	whenLeaveOpeningAuction(t, perp, points[0].t)
   854  	submitDataWithDifference(t, perp, points, 10)
   855  
   856  	// query margin factor before update
   857  	lastPoint := points[len(points)-1]
   858  	inc := perp.perpetual.GetMarginIncrease(lastPoint.t)
   859  	assert.Equal(t, "5", inc.String())
   860  
   861  	// do the perps update with a new margin factor
   862  	update := getTestPerpProd(t)
   863  	update.MarginFundingFactor = num.DecimalFromFloat(1)
   864  	err := perp.perpetual.Update(ctx, &types.InstrumentPerps{Perps: update}, perp.oe)
   865  	require.NoError(t, err)
   866  
   867  	// expect two unsubscriptions
   868  	assert.Equal(t, perp.unsub, 2)
   869  
   870  	// margin increase should now be double, which means the data-points were preserved
   871  	inc = perp.perpetual.GetMarginIncrease(lastPoint.t)
   872  	assert.Equal(t, "10", inc.String())
   873  
   874  	// now submit a data point and check it is expected i.e the funding period is still active
   875  	perp.broker.EXPECT().Send(gomock.Any()).Times(1)
   876  	assert.NoError(t, perp.perpetual.SubmitDataPoint(ctx, num.NewUint(123), lastPoint.t+int64(time.Hour)))
   877  }
   878  
   879  func testFundingPaymentOnStartBoundary(t *testing.T) {
   880  	perp := testPerpetual(t)
   881  	defer perp.ctrl.Finish()
   882  	// set of the data points such that difference in averages is 0
   883  	points := getTestDataPoints(t)
   884  
   885  	// tell the perpetual that we are ready to accept settlement stuff
   886  	st := points[0].t
   887  	whenLeaveOpeningAuction(t, perp, st)
   888  
   889  	expectedTWAP := 100
   890  	// send in data points at this time
   891  	submitPointWithDifference(t, perp, points[0], expectedTWAP)
   892  
   893  	// now get the funding-payment at this time
   894  	fundingPayment := getFundingPayment(t, perp, st)
   895  	assert.Equal(t, "100", fundingPayment)
   896  }
   897  
   898  func TestFundingPaymentModifiers(t *testing.T) {
   899  	cases := []struct {
   900  		twapDifference         int
   901  		scalingFactor          *num.Decimal
   902  		upperBound             *num.Decimal
   903  		lowerBound             *num.Decimal
   904  		expectedFundingPayment string
   905  		expectedFundingRate    string
   906  	}{
   907  		{
   908  			twapDifference:         220,
   909  			scalingFactor:          ptr.From(num.DecimalFromFloat(0.5)),
   910  			expectedFundingPayment: "110",
   911  			expectedFundingRate:    "1",
   912  		},
   913  		{
   914  			twapDifference:         1100,
   915  			scalingFactor:          ptr.From(num.DecimalFromFloat(1.5)),
   916  			expectedFundingPayment: "1650",
   917  			expectedFundingRate:    "15",
   918  		},
   919  		{
   920  			twapDifference:         100,
   921  			upperBound:             ptr.From(num.DecimalFromFloat(0.5)),
   922  			expectedFundingPayment: "55", // 0.5 * external-twap < diff, so snap to 0.5
   923  			expectedFundingRate:    "0.5",
   924  		},
   925  		{
   926  			twapDifference:         5,
   927  			lowerBound:             ptr.From(num.DecimalFromFloat(0.5)),
   928  			expectedFundingPayment: "55", // 0.5 * external-twap > 5, so snap to 0.5
   929  			expectedFundingRate:    "0.5",
   930  		},
   931  		{
   932  			twapDifference:         1100,
   933  			scalingFactor:          ptr.From(num.DecimalFromFloat(1.5)),
   934  			upperBound:             ptr.From(num.DecimalFromFloat(0.5)),
   935  			expectedFundingPayment: "55",
   936  			expectedFundingRate:    "0.5",
   937  		},
   938  	}
   939  
   940  	for _, c := range cases {
   941  		perp := testPerpetual(t)
   942  		defer perp.ctrl.Finish()
   943  
   944  		// set modifiers
   945  		perp.perp.FundingRateScalingFactor = c.scalingFactor
   946  		perp.perp.FundingRateLowerBound = c.lowerBound
   947  		perp.perp.FundingRateUpperBound = c.upperBound
   948  
   949  		// tell the perpetual that we are ready to accept settlement stuff
   950  		points := getTestDataPoints(t)
   951  		whenLeaveOpeningAuction(t, perp, points[0].t)
   952  		submitPointWithDifference(t, perp, points[0], c.twapDifference)
   953  
   954  		// check the goods
   955  		fundingPayment := getFundingPayment(t, perp, points[0].t)
   956  		assert.Equal(t, c.expectedFundingPayment, fundingPayment)
   957  
   958  		fundingRate := getFundingRate(t, perp, points[0].t)
   959  		assert.Equal(t, c.expectedFundingRate, fundingRate)
   960  	}
   961  }
   962  
   963  // submits the given data points as both external and interval but with the given different added to the internal price.
   964  func submitDataWithDifference(t *testing.T, perp *tstPerp, points []*testDataPoint, diff int) {
   965  	t.Helper()
   966  	for _, p := range points {
   967  		submitPointWithDifference(t, perp, p, diff)
   968  	}
   969  }
   970  
   971  // submits the single data point as both external and internal but with a differece in price.
   972  func submitPointWithDifference(t *testing.T, perp *tstPerp, p *testDataPoint, diff int) {
   973  	t.Helper()
   974  	ctx := context.Background()
   975  
   976  	var internalPrice *num.Uint
   977  	perp.broker.EXPECT().Send(gomock.Any()).Times(2)
   978  	perp.perpetual.AddTestExternalPoint(ctx, p.price, p.t)
   979  	internalPrice = p.price.Clone()
   980  
   981  	if diff > 0 {
   982  		internalPrice = num.UintZero().Add(p.price, num.NewUint(uint64(diff)))
   983  	}
   984  	if diff < 0 {
   985  		internalPrice = num.UintZero().Sub(p.price, num.NewUint(uint64(-diff)))
   986  	}
   987  	require.NoError(t, perp.perpetual.SubmitDataPoint(ctx, internalPrice, p.t))
   988  }
   989  
   990  func whenLeaveOpeningAuction(t *testing.T, perp *tstPerp, now int64) {
   991  	t.Helper()
   992  	perp.broker.EXPECT().Send(gomock.Any()).Times(1)
   993  	whenAuctionStateChanges(t, perp, now, false)
   994  }
   995  
   996  func whenAuctionStateChanges(t *testing.T, perp *tstPerp, now int64, enter bool) {
   997  	t.Helper()
   998  	perp.ts.EXPECT().GetTimeNow().Times(1).Return(time.Unix(0, now))
   999  	perp.perpetual.UpdateAuctionState(context.Background(), enter)
  1000  }
  1001  
  1002  type testDataPoint struct {
  1003  	price *num.Uint
  1004  	t     int64
  1005  }
  1006  
  1007  func getTestDataPoints(t *testing.T) []*testDataPoint {
  1008  	t.Helper()
  1009  
  1010  	// interest-rates are daily so we want the time of the data-points to be of that scale
  1011  	// so we make them over 6 hours, a quarter of a day.
  1012  
  1013  	year := 31536000000000000
  1014  	month := int64(year / 12)
  1015  	st := int64(time.Hour)
  1016  
  1017  	return []*testDataPoint{
  1018  		{
  1019  			price: num.NewUint(110),
  1020  			t:     st,
  1021  		},
  1022  		{
  1023  			price: num.NewUint(120),
  1024  			t:     st + month,
  1025  		},
  1026  		{
  1027  			price: num.NewUint(120),
  1028  			t:     st + (month * 2),
  1029  		},
  1030  		{
  1031  			price: num.NewUint(100),
  1032  			t:     st + (month * 3),
  1033  		},
  1034  	}
  1035  }
  1036  
  1037  type tstPerp struct {
  1038  	oe        *mocks.MockOracleEngine
  1039  	ts        *tmocks.MockTimeService
  1040  	broker    *mocks.MockBroker
  1041  	perpetual *products.Perpetual
  1042  	ctrl      *gomock.Controller
  1043  	perp      *types.Perps
  1044  
  1045  	unsub int
  1046  }
  1047  
  1048  func (tp *tstPerp) unsubscribe(_ context.Context, _ spec.SubscriptionID) {
  1049  	tp.unsub++
  1050  }
  1051  
  1052  func testPerpetual(t *testing.T) *tstPerp {
  1053  	t.Helper()
  1054  
  1055  	log := logging.NewTestLogger()
  1056  	ctrl := gomock.NewController(t)
  1057  	oe := mocks.NewMockOracleEngine(ctrl)
  1058  	broker := mocks.NewMockBroker(ctrl)
  1059  	ts := tmocks.NewMockTimeService(ctrl)
  1060  	perp := getTestPerpProd(t)
  1061  
  1062  	tp := &tstPerp{
  1063  		ts:     ts,
  1064  		oe:     oe,
  1065  		broker: broker,
  1066  		ctrl:   ctrl,
  1067  		perp:   perp,
  1068  	}
  1069  	tp.oe.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(spec.SubscriptionID(1), tp.unsubscribe, nil)
  1070  
  1071  	perpetual, err := products.NewPerpetual(context.Background(), log, perp, "", ts, oe, broker, 1)
  1072  	if err != nil {
  1073  		t.Fatalf("couldn't create a perp for testing: %v", err)
  1074  	}
  1075  
  1076  	perpetual.NotifyOnDataSourcePropagation(func(context.Context, *num.Uint) {})
  1077  
  1078  	tp.perpetual = perpetual
  1079  	return tp
  1080  }
  1081  
  1082  func getTestPerpProd(t *testing.T) *types.Perps {
  1083  	t.Helper()
  1084  	dp := uint32(1)
  1085  	pubKeys := []*dstypes.Signer{
  1086  		dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey),
  1087  	}
  1088  
  1089  	factor, _ := num.DecimalFromString("0.5")
  1090  	settlementSrc := &datasource.Spec{
  1091  		Data: datasource.NewDefinition(
  1092  			datasource.ContentTypeOracle,
  1093  		).SetOracleConfig(
  1094  			&signedoracle.SpecConfiguration{
  1095  				Signers: pubKeys,
  1096  				Filters: []*dstypes.SpecFilter{
  1097  					{
  1098  						Key: &dstypes.SpecPropertyKey{
  1099  							Name:                "foo",
  1100  							Type:                datapb.PropertyKey_TYPE_INTEGER,
  1101  							NumberDecimalPlaces: ptr.From(uint64(dp)),
  1102  						},
  1103  						Conditions: nil,
  1104  					},
  1105  				},
  1106  			},
  1107  		),
  1108  	}
  1109  
  1110  	scheduleSrc := &datasource.Spec{
  1111  		Data: datasource.NewDefinition(
  1112  			datasource.ContentTypeOracle,
  1113  		).SetOracleConfig(&signedoracle.SpecConfiguration{
  1114  			Signers: pubKeys,
  1115  			Filters: []*dstypes.SpecFilter{
  1116  				{
  1117  					Key: &dstypes.SpecPropertyKey{
  1118  						Name: "bar",
  1119  						Type: datapb.PropertyKey_TYPE_TIMESTAMP,
  1120  					},
  1121  					Conditions: nil,
  1122  				},
  1123  			},
  1124  		}),
  1125  	}
  1126  
  1127  	return &types.Perps{
  1128  		MarginFundingFactor:                 factor,
  1129  		DataSourceSpecForSettlementData:     settlementSrc,
  1130  		DataSourceSpecForSettlementSchedule: scheduleSrc,
  1131  		DataSourceSpecBinding: &datasource.SpecBindingForPerps{
  1132  			SettlementDataProperty:     "foo",
  1133  			SettlementScheduleProperty: "bar",
  1134  		},
  1135  	}
  1136  }
  1137  
  1138  type DataPoint struct {
  1139  	price *num.Uint
  1140  	t     int64
  1141  	seq   int
  1142  	twap  *num.Uint
  1143  }
  1144  
  1145  type FundingNode struct {
  1146  	Timestamp time.Time `json:"timestamp"`
  1147  	Seq       int       `json:"seq"`
  1148  	Price     string    `json:"price"`
  1149  	TWAP      string    `json:"twap"`
  1150  	Source    string    `json:"dataPointSource"`
  1151  }
  1152  
  1153  type Edge struct {
  1154  	Node FundingNode `json:"node"`
  1155  }
  1156  
  1157  type FundingDataPoints struct {
  1158  	Edges []Edge `json:"edges"`
  1159  }
  1160  
  1161  type GQLData struct {
  1162  	FundingDataPoints FundingDataPoints `json:"fundingPeriodDataPoints"`
  1163  }
  1164  
  1165  type GQL struct {
  1166  	Data GQLData `json:"data"`
  1167  }
  1168  
  1169  const testData = `{
  1170    "data": {
  1171      "fundingPeriodDataPoints": {
  1172        "edges": [
  1173          {
  1174            "node": {
  1175              "timestamp": "2023-08-16T13:52:00Z",
  1176              "seq": 6,
  1177              "price": "29124220000",
  1178              "twap": "29124220000",
  1179              "dataPointSource": "SOURCE_EXTERNAL"
  1180            }
  1181          },
  1182          {
  1183            "node": {
  1184              "timestamp": "2023-08-16T13:51:36Z",
  1185              "seq": 6,
  1186              "price": "29124220000",
  1187              "twap": "29124220000",
  1188              "dataPointSource": "SOURCE_EXTERNAL"
  1189            }
  1190          },
  1191          {
  1192            "node": {
  1193              "timestamp": "2023-08-16T13:51:00Z",
  1194              "seq": 6,
  1195              "price": "29124220000",
  1196              "twap": "29124220000",
  1197              "dataPointSource": "SOURCE_EXTERNAL"
  1198            }
  1199          },
  1200          {
  1201            "node": {
  1202              "timestamp": "2023-08-16T13:50:36Z",
  1203              "seq": 6,
  1204              "price": "29124220000",
  1205              "twap": "29124220000",
  1206              "dataPointSource": "SOURCE_EXTERNAL"
  1207            }
  1208          },
  1209          {
  1210            "node": {
  1211              "timestamp": "2023-08-16T13:50:00Z",
  1212              "seq": 6,
  1213              "price": "29124220000",
  1214              "twap": "29124220000",
  1215              "dataPointSource": "SOURCE_EXTERNAL"
  1216            }
  1217          },
  1218          {
  1219            "node": {
  1220              "timestamp": "2023-08-16T13:49:36Z",
  1221              "seq": 6,
  1222              "price": "29124220000",
  1223              "twap": "29124220000",
  1224              "dataPointSource": "SOURCE_EXTERNAL"
  1225            }
  1226          },
  1227          {
  1228            "node": {
  1229              "timestamp": "2023-08-16T13:49:00Z",
  1230              "seq": 5,
  1231              "price": "29124220000",
  1232              "twap": "29124220000",
  1233              "dataPointSource": "SOURCE_EXTERNAL"
  1234            }
  1235          },
  1236          {
  1237            "node": {
  1238              "timestamp": "2023-08-16T13:48:36Z",
  1239              "seq": 5,
  1240              "price": "29124220000",
  1241              "twap": "29124220000",
  1242              "dataPointSource": "SOURCE_EXTERNAL"
  1243            }
  1244          },
  1245          {
  1246            "node": {
  1247              "timestamp": "2023-08-16T13:48:12Z",
  1248              "seq": 5,
  1249              "price": "29124220000",
  1250              "twap": "29124220000",
  1251              "dataPointSource": "SOURCE_EXTERNAL"
  1252            }
  1253          },
  1254          {
  1255            "node": {
  1256              "timestamp": "2023-08-16T13:47:36Z",
  1257              "seq": 5,
  1258              "price": "29124220000",
  1259              "twap": "29124220000",
  1260              "dataPointSource": "SOURCE_EXTERNAL"
  1261            }
  1262          },
  1263          {
  1264            "node": {
  1265              "timestamp": "2023-08-16T13:47:00Z",
  1266              "seq": 5,
  1267              "price": "29124220000",
  1268              "twap": "29124220000",
  1269              "dataPointSource": "SOURCE_EXTERNAL"
  1270            }
  1271          },
  1272          {
  1273            "node": {
  1274              "timestamp": "2023-08-16T13:46:36Z",
  1275              "seq": 5,
  1276              "price": "29124220000",
  1277              "twap": "29124220000",
  1278              "dataPointSource": "SOURCE_EXTERNAL"
  1279            }
  1280          },
  1281          {
  1282            "node": {
  1283              "timestamp": "2023-08-16T13:46:00Z",
  1284              "seq": 5,
  1285              "price": "29124220000",
  1286              "twap": "29124220000",
  1287              "dataPointSource": "SOURCE_EXTERNAL"
  1288            }
  1289          },
  1290          {
  1291            "node": {
  1292              "timestamp": "2023-08-16T13:45:36Z",
  1293              "seq": 5,
  1294              "price": "29124220000",
  1295              "twap": "29124220000",
  1296              "dataPointSource": "SOURCE_EXTERNAL"
  1297            }
  1298          },
  1299          {
  1300            "node": {
  1301              "timestamp": "2023-08-16T13:45:00Z",
  1302              "seq": 5,
  1303              "price": "29124220000",
  1304              "twap": "29124220000",
  1305              "dataPointSource": "SOURCE_EXTERNAL"
  1306            }
  1307          },
  1308          {
  1309            "node": {
  1310              "timestamp": "2023-08-16T13:44:36Z",
  1311              "seq": 5,
  1312              "price": "29124220000",
  1313              "twap": "29124220000",
  1314              "dataPointSource": "SOURCE_EXTERNAL"
  1315            }
  1316          },
  1317          {
  1318            "node": {
  1319              "timestamp": "2023-08-16T13:44:00Z",
  1320              "seq": 4,
  1321              "price": "29124220000",
  1322              "twap": "29124220000",
  1323              "dataPointSource": "SOURCE_EXTERNAL"
  1324            }
  1325          },
  1326          {
  1327            "node": {
  1328              "timestamp": "2023-08-16T13:43:36Z",
  1329              "seq": 4,
  1330              "price": "29124220000",
  1331              "twap": "29124220000",
  1332              "dataPointSource": "SOURCE_EXTERNAL"
  1333            }
  1334          },
  1335          {
  1336            "node": {
  1337              "timestamp": "2023-08-16T13:43:00Z",
  1338              "seq": 4,
  1339              "price": "29124220000",
  1340              "twap": "29124220000",
  1341              "dataPointSource": "SOURCE_EXTERNAL"
  1342            }
  1343          },
  1344          {
  1345            "node": {
  1346              "timestamp": "2023-08-16T13:42:36Z",
  1347              "seq": 4,
  1348              "price": "29124220000",
  1349              "twap": "29124220000",
  1350              "dataPointSource": "SOURCE_EXTERNAL"
  1351            }
  1352          },
  1353          {
  1354            "node": {
  1355              "timestamp": "2023-08-16T13:42:00Z",
  1356              "seq": 4,
  1357              "price": "29124220000",
  1358              "twap": "29124220000",
  1359              "dataPointSource": "SOURCE_EXTERNAL"
  1360            }
  1361          },
  1362          {
  1363            "node": {
  1364              "timestamp": "2023-08-16T13:41:36Z",
  1365              "seq": 4,
  1366              "price": "29124220000",
  1367              "twap": "29124220000",
  1368              "dataPointSource": "SOURCE_EXTERNAL"
  1369            }
  1370          },
  1371          {
  1372            "node": {
  1373              "timestamp": "2023-08-16T13:41:24Z",
  1374              "seq": 4,
  1375              "price": "29124220000",
  1376              "twap": "29124220000",
  1377              "dataPointSource": "SOURCE_EXTERNAL"
  1378            }
  1379          },
  1380          {
  1381            "node": {
  1382              "timestamp": "2023-08-16T13:40:36Z",
  1383              "seq": 4,
  1384              "price": "29124220000",
  1385              "twap": "29124220000",
  1386              "dataPointSource": "SOURCE_EXTERNAL"
  1387            }
  1388          },
  1389          {
  1390            "node": {
  1391              "timestamp": "2023-08-16T13:40:00Z",
  1392              "seq": 4,
  1393              "price": "29124220000",
  1394              "twap": "29124220000",
  1395              "dataPointSource": "SOURCE_EXTERNAL"
  1396            }
  1397          },
  1398          {
  1399            "node": {
  1400              "timestamp": "2023-08-16T13:39:36Z",
  1401              "seq": 4,
  1402              "price": "29124220000",
  1403              "twap": "29124220000",
  1404              "dataPointSource": "SOURCE_EXTERNAL"
  1405            }
  1406          },
  1407          {
  1408            "node": {
  1409              "timestamp": "2023-08-16T13:39:12Z",
  1410              "seq": 4,
  1411              "price": "29124220000",
  1412              "twap": "29124220000",
  1413              "dataPointSource": "SOURCE_EXTERNAL"
  1414            }
  1415          },
  1416          {
  1417            "node": {
  1418              "timestamp": "2023-08-16T13:39:12Z",
  1419              "seq": 3,
  1420              "price": "29124220000",
  1421              "twap": "29124220000",
  1422              "dataPointSource": "SOURCE_EXTERNAL"
  1423            }
  1424          },
  1425          {
  1426            "node": {
  1427              "timestamp": "2023-08-16T13:38:48Z",
  1428              "seq": 4,
  1429              "price": "29124220000",
  1430              "twap": "29124220000",
  1431              "dataPointSource": "SOURCE_EXTERNAL"
  1432            }
  1433          },
  1434          {
  1435            "node": {
  1436              "timestamp": "2023-08-16T13:38:12Z",
  1437              "seq": 3,
  1438              "price": "29124220000",
  1439              "twap": "29124220000",
  1440              "dataPointSource": "SOURCE_EXTERNAL"
  1441            }
  1442          },
  1443          {
  1444            "node": {
  1445              "timestamp": "2023-08-16T13:37:36Z",
  1446              "seq": 3,
  1447              "price": "29124220000",
  1448              "twap": "29124220000",
  1449              "dataPointSource": "SOURCE_EXTERNAL"
  1450            }
  1451          },
  1452          {
  1453            "node": {
  1454              "timestamp": "2023-08-16T13:37:00Z",
  1455              "seq": 3,
  1456              "price": "29124220000",
  1457              "twap": "29124220000",
  1458              "dataPointSource": "SOURCE_EXTERNAL"
  1459            }
  1460          },
  1461          {
  1462            "node": {
  1463              "timestamp": "2023-08-16T13:36:36Z",
  1464              "seq": 3,
  1465              "price": "29124220000",
  1466              "twap": "29124220000",
  1467              "dataPointSource": "SOURCE_EXTERNAL"
  1468            }
  1469          },
  1470          {
  1471            "node": {
  1472              "timestamp": "2023-08-16T13:36:00Z",
  1473              "seq": 3,
  1474              "price": "29124220000",
  1475              "twap": "29124220000",
  1476              "dataPointSource": "SOURCE_EXTERNAL"
  1477            }
  1478          },
  1479          {
  1480            "node": {
  1481              "timestamp": "2023-08-16T13:35:36Z",
  1482              "seq": 3,
  1483              "price": "29124220000",
  1484              "twap": "29124220000",
  1485              "dataPointSource": "SOURCE_EXTERNAL"
  1486            }
  1487          },
  1488          {
  1489            "node": {
  1490              "timestamp": "2023-08-16T13:35:00Z",
  1491              "seq": 3,
  1492              "price": "29124220000",
  1493              "twap": "29124220000",
  1494              "dataPointSource": "SOURCE_EXTERNAL"
  1495            }
  1496          },
  1497          {
  1498            "node": {
  1499              "timestamp": "2023-08-16T13:34:36Z",
  1500              "seq": 3,
  1501              "price": "29124220000",
  1502              "twap": "29124220000",
  1503              "dataPointSource": "SOURCE_EXTERNAL"
  1504            }
  1505          },
  1506          {
  1507            "node": {
  1508              "timestamp": "2023-08-16T13:34:00Z",
  1509              "seq": 2,
  1510              "price": "29124220000",
  1511              "twap": "29124220000",
  1512              "dataPointSource": "SOURCE_EXTERNAL"
  1513            }
  1514          },
  1515          {
  1516            "node": {
  1517              "timestamp": "2023-08-16T13:33:36Z",
  1518              "seq": 2,
  1519              "price": "29124220000",
  1520              "twap": "29124220000",
  1521              "dataPointSource": "SOURCE_EXTERNAL"
  1522            }
  1523          },
  1524          {
  1525            "node": {
  1526              "timestamp": "2023-08-16T13:33:00Z",
  1527              "seq": 2,
  1528              "price": "29124220000",
  1529              "twap": "29124220000",
  1530              "dataPointSource": "SOURCE_EXTERNAL"
  1531            }
  1532          },
  1533          {
  1534            "node": {
  1535              "timestamp": "2023-08-16T13:32:36Z",
  1536              "seq": 2,
  1537              "price": "29124220000",
  1538              "twap": "29124220000",
  1539              "dataPointSource": "SOURCE_EXTERNAL"
  1540            }
  1541          },
  1542          {
  1543            "node": {
  1544              "timestamp": "2023-08-16T13:32:00Z",
  1545              "seq": 2,
  1546              "price": "29124220000",
  1547              "twap": "29124220000",
  1548              "dataPointSource": "SOURCE_EXTERNAL"
  1549            }
  1550          },
  1551          {
  1552            "node": {
  1553              "timestamp": "2023-08-16T13:31:48Z",
  1554              "seq": 2,
  1555              "price": "29124220000",
  1556              "twap": "29124220000",
  1557              "dataPointSource": "SOURCE_EXTERNAL"
  1558            }
  1559          },
  1560          {
  1561            "node": {
  1562              "timestamp": "2023-08-16T13:31:00Z",
  1563              "seq": 2,
  1564              "price": "29124220000",
  1565              "twap": "29124220000",
  1566              "dataPointSource": "SOURCE_EXTERNAL"
  1567            }
  1568          },
  1569          {
  1570            "node": {
  1571              "timestamp": "2023-08-16T13:30:36Z",
  1572              "seq": 2,
  1573              "price": "29124220000",
  1574              "twap": "29124220000",
  1575              "dataPointSource": "SOURCE_EXTERNAL"
  1576            }
  1577          },
  1578          {
  1579            "node": {
  1580              "timestamp": "2023-08-16T13:30:00Z",
  1581              "seq": 2,
  1582              "price": "29124220000",
  1583              "twap": "29124220000",
  1584              "dataPointSource": "SOURCE_EXTERNAL"
  1585            }
  1586          },
  1587          {
  1588            "node": {
  1589              "timestamp": "2023-08-16T13:29:36Z",
  1590              "seq": 2,
  1591              "price": "29124220000",
  1592              "twap": "29124220000",
  1593              "dataPointSource": "SOURCE_EXTERNAL"
  1594            }
  1595          },
  1596          {
  1597            "node": {
  1598              "timestamp": "2023-08-16T13:29:00Z",
  1599              "seq": 1,
  1600              "price": "29124220000",
  1601              "twap": "29124220000",
  1602              "dataPointSource": "SOURCE_EXTERNAL"
  1603            }
  1604          },
  1605          {
  1606            "node": {
  1607              "timestamp": "2023-08-16T13:28:36Z",
  1608              "seq": 1,
  1609              "price": "29124220000",
  1610              "twap": "29124220000",
  1611              "dataPointSource": "SOURCE_EXTERNAL"
  1612            }
  1613          },
  1614          {
  1615            "node": {
  1616              "timestamp": "2023-08-16T13:28:00Z",
  1617              "seq": 1,
  1618              "price": "29124220000",
  1619              "twap": "29124220000",
  1620              "dataPointSource": "SOURCE_EXTERNAL"
  1621            }
  1622          },
  1623          {
  1624            "node": {
  1625              "timestamp": "2023-08-16T13:27:36Z",
  1626              "seq": 1,
  1627              "price": "29124220000",
  1628              "twap": "29124220000",
  1629              "dataPointSource": "SOURCE_EXTERNAL"
  1630            }
  1631          },
  1632          {
  1633            "node": {
  1634              "timestamp": "2023-08-16T13:27:12Z",
  1635              "seq": 1,
  1636              "price": "29124220000",
  1637              "twap": "29124220000",
  1638              "dataPointSource": "SOURCE_EXTERNAL"
  1639            }
  1640          },
  1641          {
  1642            "node": {
  1643              "timestamp": "2023-08-16T13:26:36Z",
  1644              "seq": 1,
  1645              "price": "29124220000",
  1646              "twap": "29124220000",
  1647              "dataPointSource": "SOURCE_EXTERNAL"
  1648            }
  1649          },
  1650          {
  1651            "node": {
  1652              "timestamp": "2023-08-16T13:26:00Z",
  1653              "seq": 1,
  1654              "price": "29124220000",
  1655              "twap": "29124220000",
  1656              "dataPointSource": "SOURCE_EXTERNAL"
  1657            }
  1658          },
  1659          {
  1660            "node": {
  1661              "timestamp": "2023-08-16T13:25:36Z",
  1662              "seq": 1,
  1663              "price": "29124220000",
  1664              "twap": "29124220000",
  1665              "dataPointSource": "SOURCE_EXTERNAL"
  1666            }
  1667          },
  1668          {
  1669            "node": {
  1670              "timestamp": "2023-08-16T13:25:12Z",
  1671              "seq": 1,
  1672              "price": "29124220000",
  1673              "twap": "29124220000",
  1674              "dataPointSource": "SOURCE_EXTERNAL"
  1675            }
  1676          },
  1677          {
  1678            "node": {
  1679              "timestamp": "2023-08-16T13:24:48Z",
  1680              "seq": 1,
  1681              "price": "29124220000",
  1682              "twap": "29124220000",
  1683              "dataPointSource": "SOURCE_EXTERNAL"
  1684            }
  1685          },
  1686          {
  1687            "node": {
  1688              "timestamp": "2023-08-16T13:24:00Z",
  1689              "seq": 1,
  1690              "price": "29124220000",
  1691              "twap": "29124220000",
  1692              "dataPointSource": "SOURCE_EXTERNAL"
  1693            }
  1694          }
  1695        ]
  1696      }
  1697    }
  1698  }`
  1699  
  1700  func getGQLData() (*GQL, error) {
  1701  	ret := GQL{}
  1702  	if err := json.Unmarshal([]byte(testData), &ret); err != nil {
  1703  		return nil, err
  1704  	}
  1705  	return &ret, nil
  1706  }
  1707  
  1708  func (g *GQL) Sort(reverse bool) {
  1709  	// group by sequence
  1710  	sort.SliceStable(g.Data.FundingDataPoints.Edges, func(i, j int) bool {
  1711  		if g.Data.FundingDataPoints.Edges[i].Node.Seq == g.Data.FundingDataPoints.Edges[j].Node.Seq {
  1712  			if reverse {
  1713  				return g.Data.FundingDataPoints.Edges[i].Node.Timestamp.UnixNano() > g.Data.FundingDataPoints.Edges[j].Node.Timestamp.UnixNano()
  1714  			}
  1715  			return g.Data.FundingDataPoints.Edges[i].Node.Timestamp.UnixNano() < g.Data.FundingDataPoints.Edges[j].Node.Timestamp.UnixNano()
  1716  		}
  1717  
  1718  		return g.Data.FundingDataPoints.Edges[i].Node.Seq < g.Data.FundingDataPoints.Edges[j].Node.Seq
  1719  	})
  1720  }
  1721  
  1722  func (g *GQL) GetDataPoints(reverse bool) []DataPoint {
  1723  	g.Sort(reverse)
  1724  	ret := make([]DataPoint, 0, len(g.Data.FundingDataPoints.Edges))
  1725  	for _, n := range g.Data.FundingDataPoints.Edges {
  1726  		p, _ := num.UintFromString(n.Node.Price, 10)
  1727  		twap, _ := num.UintFromString(n.Node.TWAP, 10)
  1728  		ret = append(ret, DataPoint{
  1729  			price: p,
  1730  			t:     n.Node.Timestamp.UnixNano(),
  1731  			seq:   n.Node.Seq,
  1732  			twap:  twap,
  1733  		})
  1734  	}
  1735  	return ret
  1736  }