code.vegaprotocol.io/vega@v0.79.0/core/liquidity/target/spot/engine_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 spot_test
    17  
    18  import (
    19  	"testing"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/liquidity/target/spot"
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  var (
    30  	now      = time.Date(2020, 10, 30, 9, 0, 0, 0, time.UTC)
    31  	marketID = "market-1"
    32  )
    33  
    34  func TestConstructor(t *testing.T) {
    35  	params := types.TargetStakeParameters{TimeWindow: 3600, ScalingFactor: num.DecimalFromFloat(10)}
    36  	engine := spot.NewEngine(params, marketID, num.DecimalFromFloat(1))
    37  
    38  	require.NotNil(t, engine)
    39  }
    40  
    41  func TestTotalStake(t *testing.T) {
    42  	params := types.TargetStakeParameters{TimeWindow: 3600, ScalingFactor: num.DecimalFromFloat(10)}
    43  	engine := spot.NewEngine(params, marketID, num.DecimalFromFloat(1))
    44  	err := engine.RecordTotalStake(9, now)
    45  	require.NoError(t, err)
    46  	err = engine.RecordTotalStake(0, now)
    47  	require.NoError(t, err)
    48  	err = engine.RecordTotalStake(11, now.Add(time.Nanosecond))
    49  	require.NoError(t, err)
    50  	err = engine.RecordTotalStake(12, now.Add(time.Nanosecond))
    51  	require.NoError(t, err)
    52  	err = engine.RecordTotalStake(13, now.Add(-2*time.Nanosecond))
    53  	require.Error(t, err)
    54  }
    55  
    56  func TestGetTargetStake_NoRecordedTotalStake(t *testing.T) {
    57  	params := types.TargetStakeParameters{TimeWindow: 3600, ScalingFactor: num.DecimalFromFloat(10)}
    58  	engine := spot.NewEngine(params, marketID, num.DecimalFromFloat(1))
    59  	targetStake := engine.GetTargetStake(now)
    60  	require.Equal(t, num.UintZero(), targetStake)
    61  }
    62  
    63  func TestGetTargetStake_VerifyMaxOI(t *testing.T) {
    64  	tWindow := 60 * time.Minute
    65  	scalingFactor := num.DecimalFromFloat(0.25)
    66  	params := types.TargetStakeParameters{TimeWindow: int64(tWindow.Seconds()), ScalingFactor: scalingFactor}
    67  	expectedTargetStake := func(oi uint64) *num.Uint {
    68  		mp := num.NewUint(oi).ToDecimal().Mul(scalingFactor)
    69  		ump, _ := num.UintFromDecimal(mp)
    70  		return ump
    71  	}
    72  
    73  	engine := spot.NewEngine(params, marketID, num.DecimalFromFloat(1))
    74  
    75  	// Max in current time
    76  	var maxOI uint64 = 23
    77  	err := engine.RecordTotalStake(maxOI, now)
    78  	require.NoError(t, err)
    79  	actualTargetStake1 := engine.GetTargetStake(now)
    80  	actualTargetStake2 := engine.GetTargetStake(now.Add(time.Minute))
    81  
    82  	exp := expectedTargetStake(maxOI)
    83  	require.Equal(t, exp, actualTargetStake1)
    84  	require.Equal(t, exp, actualTargetStake2)
    85  	// Max in past
    86  	now = now.Add(time.Nanosecond)
    87  	err = engine.RecordTotalStake(maxOI-1, now)
    88  	require.NoError(t, err)
    89  	actualTargetStake1 = engine.GetTargetStake(now)
    90  	actualTargetStake2 = engine.GetTargetStake(now.Add(time.Minute))
    91  
    92  	exp = expectedTargetStake(maxOI)
    93  	require.Equal(t, exp, actualTargetStake1)
    94  	require.Equal(t, exp, actualTargetStake2)
    95  
    96  	// Max in current time
    97  	now = now.Add(time.Second)
    98  	maxOI = 10 * maxOI
    99  	err = engine.RecordTotalStake(maxOI, now)
   100  	require.NoError(t, err)
   101  	actualTargetStake1 = engine.GetTargetStake(now)
   102  	actualTargetStake2 = engine.GetTargetStake(now.Add(time.Minute))
   103  
   104  	exp = expectedTargetStake(maxOI)
   105  	require.Equal(t, exp, actualTargetStake1)
   106  	require.Equal(t, exp, actualTargetStake2)
   107  
   108  	// Max in past, move time beyond window, don't update OI, max OI should be the last recorded value
   109  	now = now.Add(time.Minute)
   110  	var lastRecordedValue uint64 = 1
   111  	err = engine.RecordTotalStake(lastRecordedValue, now)
   112  	require.NoError(t, err)
   113  	now = now.Add(3 * tWindow)
   114  	actualTargetStake1 = engine.GetTargetStake(now)
   115  	actualTargetStake2 = engine.GetTargetStake(now.Add(time.Minute))
   116  
   117  	exp = expectedTargetStake(lastRecordedValue)
   118  	require.Equal(t, exp, actualTargetStake1)
   119  	require.Equal(t, exp, actualTargetStake2)
   120  
   121  	// Max in past with smaller value after it, move time beyond window so that the current max gets dropped, now target stake should be based on next value
   122  	now = now.Add(time.Minute)
   123  	var penultimateValue uint64 = 1000
   124  	err = engine.RecordTotalStake(penultimateValue, now)
   125  	require.NoError(t, err)
   126  	// Half a time window
   127  	now = now.Add(30 * time.Minute)
   128  	lastRecordedValue = 5
   129  	err = engine.RecordTotalStake(lastRecordedValue, now)
   130  	require.NoError(t, err)
   131  	// Move entire time window and a bit
   132  	now = now.Add(61 * time.Minute)
   133  	actualTargetStake1 = engine.GetTargetStake(now)
   134  	actualTargetStake2 = engine.GetTargetStake(now.Add(time.Minute))
   135  
   136  	exp = expectedTargetStake(lastRecordedValue)
   137  	require.Equal(t, exp, actualTargetStake1)
   138  	require.Equal(t, exp, actualTargetStake2)
   139  
   140  	// Max in past with OI of 0 value after it, move time beyond window so that the current max gets dropped, now target stake should be 0
   141  	now = now.Add(time.Minute)
   142  	penultimateValue = 1000
   143  	err = engine.RecordTotalStake(penultimateValue, now)
   144  	require.NoError(t, err)
   145  	// Half a time window
   146  	now = now.Add(30 * time.Minute)
   147  	lastRecordedValueIsZero := uint64(0)
   148  	err = engine.RecordTotalStake(lastRecordedValueIsZero, now)
   149  	require.NoError(t, err)
   150  	// Move entire time window and a bit
   151  	now = now.Add(61 * time.Minute)
   152  	actualTargetStake1 = engine.GetTargetStake(now)
   153  	actualTargetStake2 = engine.GetTargetStake(now.Add(time.Minute))
   154  
   155  	exp = expectedTargetStake(lastRecordedValueIsZero)
   156  	require.Equal(t, exp, actualTargetStake1)
   157  	require.Equal(t, exp, actualTargetStake2)
   158  }