code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/stake_linking_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  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/datanode/entities"
    25  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    26  	"code.vegaprotocol.io/vega/libs/num"
    27  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    28  
    29  	"github.com/georgysavva/scany/pgxscan"
    30  	"github.com/shopspring/decimal"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestStakeLinkingStore(t *testing.T) {
    36  	t.Run("Upsert should add a stake linking record if it doesn't exist in the current block", testUpsertShouldAddNewInBlock)
    37  	t.Run("Upsert should update a stake linking record if it already exists in the current block", testUpsertShouldUpdateExistingInBlock)
    38  	t.Run("GetStake should return the most current version of each stake linking record and calculate the total stake available", testGetStake)
    39  }
    40  
    41  func setupStakeLinkingTest(t *testing.T) (*sqlstore.Blocks, *sqlstore.StakeLinking) {
    42  	t.Helper()
    43  	bs := sqlstore.NewBlocks(connectionSource)
    44  	sl := sqlstore.NewStakeLinking(connectionSource)
    45  	return bs, sl
    46  }
    47  
    48  func testUpsertShouldAddNewInBlock(t *testing.T) {
    49  	ctx := tempTransaction(t)
    50  
    51  	bs, sl := setupStakeLinkingTest(t)
    52  
    53  	var rowCount int
    54  	assert.NoError(t, connectionSource.QueryRow(ctx, "select count(*) from stake_linking").Scan(&rowCount))
    55  	assert.Equal(t, 0, rowCount)
    56  
    57  	block := addTestBlock(t, ctx, bs)
    58  	stakingProtos := getStakingProtos()
    59  
    60  	proto := stakingProtos[0]
    61  	data, err := entities.StakeLinkingFromProto(proto, generateTxHash(), block.VegaTime)
    62  	require.NoError(t, err)
    63  	assert.NoError(t, sl.Upsert(ctx, data))
    64  
    65  	assert.NoError(t, connectionSource.QueryRow(ctx, "select count(*) from stake_linking").Scan(&rowCount))
    66  	assert.Equal(t, 1, rowCount)
    67  }
    68  
    69  func testUpsertShouldUpdateExistingInBlock(t *testing.T) {
    70  	ctx := tempTransaction(t)
    71  
    72  	bs, sl := setupStakeLinkingTest(t)
    73  	var rowCount int
    74  	assert.NoError(t, connectionSource.QueryRow(ctx, "select count(*) from stake_linking").Scan(&rowCount))
    75  	assert.Equal(t, 0, rowCount)
    76  
    77  	block := addTestBlock(t, ctx, bs)
    78  	stakingProtos := getStakingProtos()
    79  
    80  	for _, proto := range stakingProtos {
    81  		data, err := entities.StakeLinkingFromProto(proto, generateTxHash(), block.VegaTime)
    82  		require.NoError(t, err)
    83  		assert.NoError(t, sl.Upsert(ctx, data))
    84  	}
    85  
    86  	assert.NoError(t, connectionSource.QueryRow(ctx, "select count(*) from stake_linking").Scan(&rowCount))
    87  	assert.Equal(t, 2, rowCount)
    88  }
    89  
    90  func testGetStake(t *testing.T) {
    91  	ctx := tempTransaction(t)
    92  
    93  	bs, sl := setupStakeLinkingTest(t)
    94  
    95  	var rowCount int
    96  	assert.NoError(t, connectionSource.QueryRow(ctx, "select count(*) from stake_linking").Scan(&rowCount))
    97  	assert.Equal(t, 0, rowCount)
    98  
    99  	block := addTestBlock(t, ctx, bs)
   100  	stakingProtos := getStakingProtos()
   101  
   102  	for _, proto := range stakingProtos {
   103  		data, err := entities.StakeLinkingFromProto(proto, generateTxHash(), block.VegaTime)
   104  		require.NoError(t, err)
   105  		assert.NoError(t, sl.Upsert(ctx, data))
   106  	}
   107  
   108  	assert.NoError(t, connectionSource.QueryRow(ctx, "select count(*) from stake_linking").Scan(&rowCount))
   109  	assert.Equal(t, 2, rowCount)
   110  
   111  	partyID := entities.PartyID("cafed00d")
   112  
   113  	currentBalance, links, _, err := sl.GetStake(ctx, partyID, entities.CursorPagination{})
   114  	require.NoError(t, err)
   115  	want := num.NewUint(30002)
   116  	assert.True(t, want.EQ(currentBalance))
   117  	assert.Equal(t, 2, len(links))
   118  }
   119  
   120  func getStakingProtos() []*eventspb.StakeLinking {
   121  	return []*eventspb.StakeLinking{
   122  		{
   123  			Id:              "deadbeef",
   124  			Type:            eventspb.StakeLinking_TYPE_LINK,
   125  			Ts:              time.Now().Unix(),
   126  			Party:           "cafed00d",
   127  			Amount:          "10000",
   128  			Status:          eventspb.StakeLinking_STATUS_ACCEPTED,
   129  			FinalizedAt:     time.Now().UnixNano(),
   130  			TxHash:          "0xfe179560b9d0cc44c5fea54c2167c1cee7ccfcabf294752a4f43fb64ddffda85",
   131  			BlockHeight:     1000000,
   132  			BlockTime:       0,
   133  			LogIndex:        100000,
   134  			EthereumAddress: "TEST",
   135  		},
   136  		{
   137  			Id:              "deadbeef",
   138  			Type:            eventspb.StakeLinking_TYPE_LINK,
   139  			Ts:              time.Now().Unix(),
   140  			Party:           "cafed00d",
   141  			Amount:          "10001",
   142  			Status:          eventspb.StakeLinking_STATUS_ACCEPTED,
   143  			FinalizedAt:     time.Now().UnixNano(),
   144  			TxHash:          "0xfe179560b9d0cc44c5fea54c2167c1cee7ccfcabf294752a4f43fb64ddffda85",
   145  			BlockHeight:     1000000,
   146  			BlockTime:       0,
   147  			LogIndex:        100000,
   148  			EthereumAddress: "TEST",
   149  		},
   150  		{
   151  			Id:              "deadbaad",
   152  			Type:            eventspb.StakeLinking_TYPE_LINK,
   153  			Ts:              time.Now().Unix(),
   154  			Party:           "cafed00d",
   155  			Amount:          "20001",
   156  			Status:          eventspb.StakeLinking_STATUS_ACCEPTED,
   157  			FinalizedAt:     time.Now().UnixNano(),
   158  			TxHash:          "0xfe179560b9d0cc44c5fea54c2167c1cee7ccfcabf294752a4f43fb64ddffda85",
   159  			BlockHeight:     1000000,
   160  			BlockTime:       0,
   161  			LogIndex:        100000,
   162  			EthereumAddress: "TEST",
   163  		},
   164  	}
   165  }
   166  
   167  func TestStakeLinkingPagination(t *testing.T) {
   168  	t.Run("should return all stake linkings if no pagination is specified", testStakeLinkingPaginationNoPagination)
   169  	t.Run("should return first page of stake linkings if first is provided", testStakeLinkingPaginationFirst)
   170  	t.Run("should return last page of stake linkings if last is provided", testStakeLinkingPaginationLast)
   171  	t.Run("should return specified page of stake linkings if first and after is specified", testStakeLinkingPaginationFirstAndAfter)
   172  	t.Run("should return specified page of stake linkings if last and before is specified", testStakeLinkingPaginationLastAndBefore)
   173  
   174  	t.Run("should return all stake linkings if no pagination is specified - newest first", testStakeLinkingPaginationNoPaginationNewestFirst)
   175  	t.Run("should return first page of stake linkings if first is provided - newest first", testStakeLinkingPaginationFirstNewestFirst)
   176  	t.Run("should return last page of stake linkings if last is provided - newest first", testStakeLinkingPaginationLastNewestFirst)
   177  	t.Run("should return specified page of stake linkings if first and after is specified - newest first", testStakeLinkingPaginationFirstAndAfterNewestFirst)
   178  	t.Run("should return specified page of stake linkings if last and before is specified - newest first", testStakeLinkingPaginationLastAndBeforeNewestFirst)
   179  }
   180  
   181  func testStakeLinkingPaginationNoPagination(t *testing.T) {
   182  	ctx := tempTransaction(t)
   183  
   184  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   185  
   186  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false)
   187  	require.NoError(t, err)
   188  	partyID := entities.PartyID("cafed00d")
   189  
   190  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   191  	require.NoError(t, err)
   192  	want := links[10:]
   193  	assert.Equal(t, want, got)
   194  	assert.Equal(t, entities.PageInfo{
   195  		HasNextPage:     false,
   196  		HasPreviousPage: false,
   197  		StartCursor:     want[0].Cursor().Encode(),
   198  		EndCursor:       want[9].Cursor().Encode(),
   199  	}, pageInfo)
   200  }
   201  
   202  func testStakeLinkingPaginationFirst(t *testing.T) {
   203  	ctx := tempTransaction(t)
   204  
   205  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   206  
   207  	first := int32(3)
   208  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   209  	require.NoError(t, err)
   210  	partyID := entities.PartyID("cafed00d")
   211  
   212  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   213  	require.NoError(t, err)
   214  	want := links[10:13]
   215  	assert.Equal(t, want, got)
   216  	assert.Equal(t, entities.PageInfo{
   217  		HasNextPage:     true,
   218  		HasPreviousPage: false,
   219  		StartCursor:     want[0].Cursor().Encode(),
   220  		EndCursor:       want[2].Cursor().Encode(),
   221  	}, pageInfo)
   222  }
   223  
   224  func testStakeLinkingPaginationLast(t *testing.T) {
   225  	ctx := tempTransaction(t)
   226  
   227  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   228  
   229  	last := int32(3)
   230  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   231  	require.NoError(t, err)
   232  	partyID := entities.PartyID("cafed00d")
   233  
   234  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   235  	require.NoError(t, err)
   236  	want := links[17:]
   237  	assert.Equal(t, want, got)
   238  	assert.Equal(t, entities.PageInfo{
   239  		HasNextPage:     false,
   240  		HasPreviousPage: true,
   241  		StartCursor:     want[0].Cursor().Encode(),
   242  		EndCursor:       want[2].Cursor().Encode(),
   243  	}, pageInfo)
   244  }
   245  
   246  func testStakeLinkingPaginationFirstAndAfter(t *testing.T) {
   247  	ctx := tempTransaction(t)
   248  
   249  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   250  
   251  	first := int32(3)
   252  	after := links[12].Cursor().Encode()
   253  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   254  	require.NoError(t, err)
   255  	partyID := entities.PartyID("cafed00d")
   256  
   257  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   258  	require.NoError(t, err)
   259  	want := links[13:16]
   260  	assert.Equal(t, want, got)
   261  	assert.Equal(t, entities.PageInfo{
   262  		HasNextPage:     true,
   263  		HasPreviousPage: true,
   264  		StartCursor:     want[0].Cursor().Encode(),
   265  		EndCursor:       want[2].Cursor().Encode(),
   266  	}, pageInfo)
   267  }
   268  
   269  func testStakeLinkingPaginationLastAndBefore(t *testing.T) {
   270  	ctx := tempTransaction(t)
   271  
   272  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   273  
   274  	last := int32(3)
   275  	before := links[17].Cursor().Encode()
   276  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   277  	require.NoError(t, err)
   278  	partyID := entities.PartyID("cafed00d")
   279  
   280  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   281  	require.NoError(t, err)
   282  	want := links[14:17]
   283  	assert.Equal(t, want, got)
   284  	assert.Equal(t, entities.PageInfo{
   285  		HasNextPage:     true,
   286  		HasPreviousPage: true,
   287  		StartCursor:     want[0].Cursor().Encode(),
   288  		EndCursor:       want[2].Cursor().Encode(),
   289  	}, pageInfo)
   290  }
   291  
   292  func testStakeLinkingPaginationNoPaginationNewestFirst(t *testing.T) {
   293  	ctx := tempTransaction(t)
   294  
   295  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   296  
   297  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   298  	require.NoError(t, err)
   299  	partyID := entities.PartyID("cafed00d")
   300  
   301  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   302  	require.NoError(t, err)
   303  	want := entities.ReverseSlice(links[10:])
   304  	assert.Equal(t, want, got)
   305  	assert.Equal(t, entities.PageInfo{
   306  		HasNextPage:     false,
   307  		HasPreviousPage: false,
   308  		StartCursor:     want[0].Cursor().Encode(),
   309  		EndCursor:       want[9].Cursor().Encode(),
   310  	}, pageInfo)
   311  }
   312  
   313  func testStakeLinkingPaginationFirstNewestFirst(t *testing.T) {
   314  	ctx := tempTransaction(t)
   315  
   316  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   317  
   318  	first := int32(3)
   319  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   320  	require.NoError(t, err)
   321  	partyID := entities.PartyID("cafed00d")
   322  
   323  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   324  	require.NoError(t, err)
   325  	want := entities.ReverseSlice(links[10:])[:3]
   326  	assert.Equal(t, want, got)
   327  	assert.Equal(t, entities.PageInfo{
   328  		HasNextPage:     true,
   329  		HasPreviousPage: false,
   330  		StartCursor:     want[0].Cursor().Encode(),
   331  		EndCursor:       want[2].Cursor().Encode(),
   332  	}, pageInfo)
   333  }
   334  
   335  func testStakeLinkingPaginationLastNewestFirst(t *testing.T) {
   336  	ctx := tempTransaction(t)
   337  
   338  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   339  
   340  	last := int32(3)
   341  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   342  	require.NoError(t, err)
   343  	partyID := entities.PartyID("cafed00d")
   344  
   345  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   346  	require.NoError(t, err)
   347  	want := entities.ReverseSlice(links[10:])[7:]
   348  	assert.Equal(t, want, got)
   349  	assert.Equal(t, entities.PageInfo{
   350  		HasNextPage:     false,
   351  		HasPreviousPage: true,
   352  		StartCursor:     want[0].Cursor().Encode(),
   353  		EndCursor:       want[2].Cursor().Encode(),
   354  	}, pageInfo)
   355  }
   356  
   357  func testStakeLinkingPaginationFirstAndAfterNewestFirst(t *testing.T) {
   358  	ctx := tempTransaction(t)
   359  
   360  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   361  
   362  	first := int32(3)
   363  	after := links[17].Cursor().Encode()
   364  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   365  	require.NoError(t, err)
   366  	partyID := entities.PartyID("cafed00d")
   367  
   368  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   369  	require.NoError(t, err)
   370  	want := entities.ReverseSlice(links[10:])[3:6]
   371  	assert.Equal(t, want, got)
   372  	assert.Equal(t, entities.PageInfo{
   373  		HasNextPage:     true,
   374  		HasPreviousPage: true,
   375  		StartCursor:     want[0].Cursor().Encode(),
   376  		EndCursor:       want[2].Cursor().Encode(),
   377  	}, pageInfo)
   378  }
   379  
   380  func testStakeLinkingPaginationLastAndBeforeNewestFirst(t *testing.T) {
   381  	ctx := tempTransaction(t)
   382  
   383  	ls, links := setupStakeLinkingPaginationTest(t, ctx)
   384  
   385  	last := int32(3)
   386  	before := links[12].Cursor().Encode()
   387  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   388  	require.NoError(t, err)
   389  	partyID := entities.PartyID("cafed00d")
   390  
   391  	_, got, pageInfo, err := ls.GetStake(ctx, partyID, pagination)
   392  	require.NoError(t, err)
   393  	want := entities.ReverseSlice(links[10:])[4:7]
   394  	assert.Equal(t, want, got)
   395  	assert.Equal(t, entities.PageInfo{
   396  		HasNextPage:     true,
   397  		HasPreviousPage: true,
   398  		StartCursor:     want[0].Cursor().Encode(),
   399  		EndCursor:       want[2].Cursor().Encode(),
   400  	}, pageInfo)
   401  }
   402  
   403  func addStakeLinking(t *testing.T, ctx context.Context, ls *sqlstore.StakeLinking, id string, partyID string, logIndex int64, block entities.Block) entities.StakeLinking {
   404  	t.Helper()
   405  	l := entities.StakeLinking{
   406  		ID:                 entities.StakeLinkingID(id),
   407  		StakeLinkingType:   entities.StakeLinkingTypeLink,
   408  		EthereumTimestamp:  block.VegaTime,
   409  		PartyID:            entities.PartyID(partyID),
   410  		Amount:             decimal.NewFromFloat(1),
   411  		StakeLinkingStatus: entities.StakeLinkingStatusAccepted,
   412  		FinalizedAt:        block.VegaTime,
   413  		ForeignTxHash:      GenerateID(),
   414  		LogIndex:           logIndex,
   415  		EthereumAddress:    "0xfe179560b9d0cc44c5fea54c2167c1cee7ccfcabf294752a4f43fb64ddffda85",
   416  		VegaTime:           block.VegaTime,
   417  	}
   418  
   419  	ls.Upsert(ctx, &l)
   420  
   421  	return l
   422  }
   423  
   424  func setupStakeLinkingPaginationTest(t *testing.T, ctx context.Context) (*sqlstore.StakeLinking, []entities.StakeLinking) {
   425  	t.Helper()
   426  	bs := sqlstore.NewBlocks(connectionSource)
   427  	ls := sqlstore.NewStakeLinking(connectionSource)
   428  
   429  	blockTime := time.Date(2022, 7, 27, 8, 0, 0, 0, time.Local)
   430  	linkings := make([]entities.StakeLinking, 20)
   431  
   432  	partyID := "cafed00d"
   433  	for i := 0; i < 10; i++ {
   434  		blockTime = blockTime.Add(time.Minute)
   435  		block := addTestBlockForTime(t, ctx, bs, blockTime)
   436  		id := int64(i + 1)
   437  		linkingID := fmt.Sprintf("deadbeef%02d", id)
   438  
   439  		linkings[i] = addStakeLinking(t, ctx, ls, linkingID, partyID, id, block)
   440  	}
   441  
   442  	for i := 0; i < 10; i++ {
   443  		blockTime = blockTime.Add(time.Minute)
   444  		block := addTestBlockForTime(t, ctx, bs, blockTime)
   445  		id := int64(i + 1)
   446  		linkingID := fmt.Sprintf("deadbeef%02d", id)
   447  		linkings[10+i] = addStakeLinking(t, ctx, ls, linkingID, partyID, id+10, block)
   448  	}
   449  	return ls, linkings
   450  }
   451  
   452  func TestStakeLinkingEnums(t *testing.T) {
   453  	t.Run("should be able to store and retrieve all stake linking statuses", testStakeLinkingStatusEnum)
   454  	t.Run("should be able to store and retrieve all stake linking types", testStakeLinkingTypeEnum)
   455  }
   456  
   457  func testStakeLinkingStatusEnum(t *testing.T) {
   458  	var stakeLinkingStatus eventspb.StakeLinking_Status
   459  	states := getEnums(t, stakeLinkingStatus)
   460  	assert.Len(t, states, 4)
   461  	for s, state := range states {
   462  		t.Run(state, func(t *testing.T) {
   463  			ctx := tempTransaction(t)
   464  
   465  			bs, sl := setupStakeLinkingTest(t)
   466  
   467  			block := addTestBlock(t, ctx, bs)
   468  			stakingProtos := getStakingProtos()
   469  
   470  			proto := stakingProtos[0]
   471  			proto.Status = eventspb.StakeLinking_Status(s)
   472  			data, err := entities.StakeLinkingFromProto(proto, generateTxHash(), block.VegaTime)
   473  			require.NoError(t, err)
   474  			assert.NoError(t, sl.Upsert(ctx, data))
   475  
   476  			_, got, _, err := sl.GetStake(ctx, data.PartyID, entities.CursorPagination{})
   477  			require.NoError(t, err)
   478  			assert.Len(t, got, 1)
   479  			assert.Equal(t, data.StakeLinkingStatus, got[0].StakeLinkingStatus)
   480  		})
   481  	}
   482  }
   483  
   484  func testStakeLinkingTypeEnum(t *testing.T) {
   485  	var stakeLinkingType eventspb.StakeLinking_Type
   486  	types := getEnums(t, stakeLinkingType)
   487  	assert.Len(t, types, 3)
   488  	for ty, typ := range types {
   489  		t.Run(typ, func(t *testing.T) {
   490  			ctx := tempTransaction(t)
   491  
   492  			bs, sl := setupStakeLinkingTest(t)
   493  
   494  			block := addTestBlock(t, ctx, bs)
   495  			stakingProtos := getStakingProtos()
   496  
   497  			proto := stakingProtos[0]
   498  			proto.Type = eventspb.StakeLinking_Type(ty)
   499  			data, err := entities.StakeLinkingFromProto(proto, generateTxHash(), block.VegaTime)
   500  			require.NoError(t, err)
   501  			assert.NoError(t, sl.Upsert(ctx, data))
   502  
   503  			var got entities.StakeLinking
   504  
   505  			require.NoError(t, pgxscan.Get(ctx, connectionSource, &got, "SELECT * FROM stake_linking where tx_hash = $1", data.TxHash))
   506  			assert.Equal(t, data.StakeLinkingType, got.StakeLinkingType)
   507  		})
   508  	}
   509  }