code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/assets_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  
    23  	"code.vegaprotocol.io/vega/datanode/entities"
    24  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    25  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    26  
    27  	"github.com/shopspring/decimal"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  var testAssetCount int
    33  
    34  func addTestAsset(t *testing.T, ctx context.Context, as *sqlstore.Assets, block entities.Block, idPrefix ...string) entities.Asset {
    35  	t.Helper()
    36  	asset := getTestAsst(t, block, idPrefix...)
    37  
    38  	// Add it to the database
    39  	err := as.Add(ctx, asset)
    40  	require.NoError(t, err)
    41  	return asset
    42  }
    43  
    44  func getTestAsst(t *testing.T, block entities.Block, idPrefix ...string) entities.Asset {
    45  	t.Helper()
    46  	// Make an asset
    47  	testAssetCount++
    48  	quantum, _ := decimal.NewFromString("10")
    49  	assetID := GenerateID()
    50  
    51  	if len(idPrefix) > 0 && idPrefix[0] != "" {
    52  		assetID = fmt.Sprintf("%s%02d", idPrefix[0], testAssetCount)
    53  	}
    54  
    55  	return entities.Asset{
    56  		ID:                entities.AssetID(assetID),
    57  		Name:              fmt.Sprint("my test asset", testAssetCount),
    58  		Symbol:            fmt.Sprint("TEST", testAssetCount),
    59  		Decimals:          5,
    60  		Quantum:           quantum,
    61  		ChainID:           "1789",
    62  		ERC20Contract:     "0xdeadbeef",
    63  		VegaTime:          block.VegaTime,
    64  		LifetimeLimit:     decimal.New(42, 0),
    65  		WithdrawThreshold: decimal.New(81, 0),
    66  		Status:            entities.AssetStatusEnabled,
    67  		TxHash:            generateTxHash(),
    68  	}
    69  }
    70  
    71  func assetsEqual(t *testing.T, expected, actual entities.Asset) {
    72  	t.Helper()
    73  
    74  	assert.Equal(t, expected.ID, actual.ID)
    75  	assert.Equal(t, expected.Name, actual.Name)
    76  	assert.Equal(t, expected.Symbol, actual.Symbol)
    77  	assert.Equal(t, expected.Decimals, actual.Decimals)
    78  	assert.Equal(t, expected.Quantum, actual.Quantum)
    79  	assert.Equal(t, expected.ERC20Contract, actual.ERC20Contract)
    80  	assert.Equal(t, expected.VegaTime, actual.VegaTime)
    81  	assert.Equal(t, expected.ChainID, actual.ChainID)
    82  	assert.True(t, expected.LifetimeLimit.Equal(actual.LifetimeLimit))
    83  	assert.True(t, expected.WithdrawThreshold.Equal(actual.WithdrawThreshold))
    84  }
    85  
    86  // TestAssetCache tests for a bug which was discovered whereby fetching an asset by ID after
    87  // it had been updated but before the transaction was committed led to a poisoned cache that
    88  // returned stale values.
    89  func TestAssetCache(t *testing.T) {
    90  	ctx := tempTransaction(t)
    91  
    92  	bs := sqlstore.NewBlocks(connectionSource)
    93  	as := sqlstore.NewAssets(connectionSource)
    94  	block := addTestBlock(t, ctx, bs)
    95  
    96  	// A make a lovely asset
    97  	asset := addTestAsset(t, ctx, as, block, "")
    98  
    99  	// Try updating the asset to have a new symbol in the top level transaction
   100  	asset2 := asset
   101  	asset2.Symbol = "TEST2"
   102  	err := as.Add(ctx, asset2)
   103  	require.NoError(t, err)
   104  
   105  	// Should get new asset symbol immediately
   106  	fetched, err := as.GetByID(ctx, string(asset.ID))
   107  	require.NoError(t, err)
   108  	require.Equal(t, asset2, fetched)
   109  
   110  	// Now in a sub-transaction, update the asset to have another different symbol
   111  	txCtx, err := connectionSource.WithTransaction(ctx)
   112  	require.NoError(t, err)
   113  	asset3 := asset
   114  	asset3.Symbol = "TEST3"
   115  	err = as.Add(txCtx, asset3)
   116  	require.NoError(t, err)
   117  
   118  	// Transaction hasn't committed yet, we should still get the old symbol when fetching that asset
   119  	fetched, err = as.GetByID(ctx, string(asset.ID))
   120  	require.NoError(t, err)
   121  	assert.Equal(t, asset2, fetched)
   122  
   123  	// after commit, the new asset should be there already
   124  	err = connectionSource.Commit(txCtx)
   125  	require.NoError(t, err)
   126  	fetched, err = as.GetByID(ctx, string(asset.ID))
   127  	require.NoError(t, err)
   128  	assert.Equal(t, asset3, fetched)
   129  }
   130  
   131  func TestAsset(t *testing.T) {
   132  	ctx := tempTransaction(t)
   133  
   134  	bs := sqlstore.NewBlocks(connectionSource)
   135  	block := addTestBlock(t, ctx, bs)
   136  
   137  	as := sqlstore.NewAssets(connectionSource)
   138  
   139  	// Get all assets, there shouldn't be any yet
   140  	assets, err := as.GetAll(ctx)
   141  	require.NoError(t, err)
   142  	require.Empty(t, assets)
   143  
   144  	asset := addTestAsset(t, ctx, as, block)
   145  	asset2 := addTestAsset(t, ctx, as, block)
   146  
   147  	// Query and check we've got back an asset the same as the one we put in
   148  	fetchedAsset, err := as.GetByID(ctx, asset.ID.String())
   149  	assert.NoError(t, err)
   150  	assetsEqual(t, asset, fetchedAsset)
   151  
   152  	// Get all assets and make sure there's one more than there was to begin with
   153  	assets, err = as.GetAll(ctx)
   154  	assert.NoError(t, err)
   155  	assert.Len(t, assets, 2)
   156  
   157  	fetchedAssets, err := as.GetByTxHash(ctx, asset.TxHash)
   158  	assert.NoError(t, err)
   159  	assetsEqual(t, asset, fetchedAssets[0])
   160  
   161  	fetchedAssets, err = as.GetByTxHash(ctx, asset2.TxHash)
   162  	assert.NoError(t, err)
   163  	assetsEqual(t, asset2, fetchedAssets[0])
   164  }
   165  
   166  func setupAssetPaginationTest(t *testing.T, ctx context.Context) (*sqlstore.Assets, []entities.Asset) {
   167  	t.Helper()
   168  	bs := sqlstore.NewBlocks(connectionSource)
   169  	block := addTestBlock(t, ctx, bs)
   170  
   171  	as := sqlstore.NewAssets(connectionSource)
   172  
   173  	assets := make([]entities.Asset, 0, 10)
   174  
   175  	testAssetCount = 0
   176  
   177  	for i := 0; i < 10; i++ {
   178  		asset := addTestAsset(t, ctx, as, block, "deadbeef")
   179  		assets = append(assets, asset)
   180  	}
   181  
   182  	return as, assets
   183  }
   184  
   185  func TestAssets_GetAllWithCursorPagination(t *testing.T) {
   186  	t.Run("should return all deposits if no pagination is specified", testAssetsPaginationNoPagination)
   187  	t.Run("should return the first page of results if first is provided", testAssetPaginationFirst)
   188  	t.Run("should return the last page of results if last is provided", testAssetPaginationLast)
   189  	t.Run("should return the specified page of results if first and after is provided", testAssetPaginationFirstAndAfter)
   190  	t.Run("should return the specified page of results if last and before is provided", testAssetPaginationLastAndBefore)
   191  
   192  	t.Run("should return all deposits if no pagination is specified - newest first", testAssetsPaginationNoPaginationNewestFirst)
   193  	t.Run("should return the first page of results if first is provided - newest first", testAssetPaginationFirstNewestFirst)
   194  	t.Run("should return the last page of results if last is provided - newest first", testAssetPaginationLastNewestFirst)
   195  	t.Run("should return the specified page of results if first and after is provided - newest first", testAssetPaginationFirstAndAfterNewestFirst)
   196  	t.Run("should return the specified page of results if last and before is provided - newest first", testAssetPaginationLastAndBeforeNewestFirst)
   197  }
   198  
   199  func testAssetsPaginationNoPagination(t *testing.T) {
   200  	ctx := tempTransaction(t)
   201  
   202  	as, assets := setupAssetPaginationTest(t, ctx)
   203  
   204  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false)
   205  	assert.NoError(t, err)
   206  
   207  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   208  	assert.NoError(t, err)
   209  	assert.Equal(t, assets, got)
   210  	assert.Equal(t, entities.PageInfo{
   211  		HasNextPage:     false,
   212  		HasPreviousPage: false,
   213  		StartCursor:     assets[0].Cursor().Encode(),
   214  		EndCursor:       assets[9].Cursor().Encode(),
   215  	}, pageInfo)
   216  }
   217  
   218  func testAssetPaginationFirst(t *testing.T) {
   219  	ctx := tempTransaction(t)
   220  
   221  	as, assets := setupAssetPaginationTest(t, ctx)
   222  
   223  	first := int32(3)
   224  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   225  	assert.NoError(t, err)
   226  
   227  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   228  	assert.NoError(t, err)
   229  	assert.Equal(t, assets[:3], got)
   230  	assert.Equal(t, entities.PageInfo{
   231  		HasNextPage:     true,
   232  		HasPreviousPage: false,
   233  		StartCursor:     assets[0].Cursor().Encode(),
   234  		EndCursor:       assets[2].Cursor().Encode(),
   235  	}, pageInfo)
   236  }
   237  
   238  func testAssetPaginationLast(t *testing.T) {
   239  	ctx := tempTransaction(t)
   240  
   241  	as, assets := setupAssetPaginationTest(t, ctx)
   242  
   243  	last := int32(3)
   244  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   245  	assert.NoError(t, err)
   246  
   247  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   248  	assert.NoError(t, err)
   249  	assert.Equal(t, assets[7:], got)
   250  	assert.Equal(t, entities.PageInfo{
   251  		HasNextPage:     false,
   252  		HasPreviousPage: true,
   253  		StartCursor:     assets[7].Cursor().Encode(),
   254  		EndCursor:       assets[9].Cursor().Encode(),
   255  	}, pageInfo)
   256  }
   257  
   258  func testAssetPaginationFirstAndAfter(t *testing.T) {
   259  	ctx := tempTransaction(t)
   260  
   261  	as, assets := setupAssetPaginationTest(t, ctx)
   262  
   263  	first := int32(3)
   264  	after := assets[2].Cursor().Encode()
   265  
   266  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   267  	assert.NoError(t, err)
   268  
   269  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   270  	assert.NoError(t, err)
   271  	assert.Equal(t, assets[3:6], got)
   272  	assert.Equal(t, entities.PageInfo{
   273  		HasNextPage:     true,
   274  		HasPreviousPage: true,
   275  		StartCursor:     assets[3].Cursor().Encode(),
   276  		EndCursor:       assets[5].Cursor().Encode(),
   277  	}, pageInfo)
   278  }
   279  
   280  func testAssetPaginationLastAndBefore(t *testing.T) {
   281  	ctx := tempTransaction(t)
   282  
   283  	as, assets := setupAssetPaginationTest(t, ctx)
   284  
   285  	last := int32(3)
   286  	before := assets[7].Cursor().Encode()
   287  
   288  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   289  	assert.NoError(t, err)
   290  
   291  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   292  	assert.NoError(t, err)
   293  	assert.Equal(t, assets[4:7], got)
   294  	assert.Equal(t, entities.PageInfo{
   295  		HasNextPage:     true,
   296  		HasPreviousPage: true,
   297  		StartCursor:     assets[4].Cursor().Encode(),
   298  		EndCursor:       assets[6].Cursor().Encode(),
   299  	}, pageInfo)
   300  }
   301  
   302  func testAssetsPaginationNoPaginationNewestFirst(t *testing.T) {
   303  	ctx := tempTransaction(t)
   304  
   305  	as, assets := setupAssetPaginationTest(t, ctx)
   306  	assets = entities.ReverseSlice(assets)
   307  
   308  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   309  	assert.NoError(t, err)
   310  
   311  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   312  	assert.NoError(t, err)
   313  	assert.Equal(t, assets, got)
   314  	assert.Equal(t, entities.PageInfo{
   315  		HasNextPage:     false,
   316  		HasPreviousPage: false,
   317  		StartCursor:     assets[0].Cursor().Encode(),
   318  		EndCursor:       assets[9].Cursor().Encode(),
   319  	}, pageInfo)
   320  }
   321  
   322  func testAssetPaginationFirstNewestFirst(t *testing.T) {
   323  	ctx := tempTransaction(t)
   324  
   325  	as, assets := setupAssetPaginationTest(t, ctx)
   326  	assets = entities.ReverseSlice(assets)
   327  
   328  	first := int32(3)
   329  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   330  	assert.NoError(t, err)
   331  
   332  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   333  	assert.NoError(t, err)
   334  	assert.Equal(t, assets[:3], got)
   335  	assert.Equal(t, entities.PageInfo{
   336  		HasNextPage:     true,
   337  		HasPreviousPage: false,
   338  		StartCursor:     assets[0].Cursor().Encode(),
   339  		EndCursor:       assets[2].Cursor().Encode(),
   340  	}, pageInfo)
   341  }
   342  
   343  func testAssetPaginationLastNewestFirst(t *testing.T) {
   344  	ctx := tempTransaction(t)
   345  
   346  	as, assets := setupAssetPaginationTest(t, ctx)
   347  	assets = entities.ReverseSlice(assets)
   348  
   349  	last := int32(3)
   350  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   351  	assert.NoError(t, err)
   352  
   353  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   354  	assert.NoError(t, err)
   355  	assert.Equal(t, assets[7:], got)
   356  	assert.Equal(t, entities.PageInfo{
   357  		HasNextPage:     false,
   358  		HasPreviousPage: true,
   359  		StartCursor:     assets[7].Cursor().Encode(),
   360  		EndCursor:       assets[9].Cursor().Encode(),
   361  	}, pageInfo)
   362  }
   363  
   364  func testAssetPaginationFirstAndAfterNewestFirst(t *testing.T) {
   365  	ctx := tempTransaction(t)
   366  
   367  	as, assets := setupAssetPaginationTest(t, ctx)
   368  	assets = entities.ReverseSlice(assets)
   369  
   370  	first := int32(3)
   371  	after := assets[2].Cursor().Encode()
   372  
   373  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   374  	assert.NoError(t, err)
   375  
   376  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   377  	assert.NoError(t, err)
   378  	assert.Equal(t, assets[3:6], got)
   379  	assert.Equal(t, entities.PageInfo{
   380  		HasNextPage:     true,
   381  		HasPreviousPage: true,
   382  		StartCursor:     assets[3].Cursor().Encode(),
   383  		EndCursor:       assets[5].Cursor().Encode(),
   384  	}, pageInfo)
   385  }
   386  
   387  func testAssetPaginationLastAndBeforeNewestFirst(t *testing.T) {
   388  	ctx := tempTransaction(t)
   389  
   390  	as, assets := setupAssetPaginationTest(t, ctx)
   391  	assets = entities.ReverseSlice(assets)
   392  
   393  	last := int32(3)
   394  	before := assets[7].Cursor().Encode()
   395  
   396  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   397  	assert.NoError(t, err)
   398  
   399  	got, pageInfo, err := as.GetAllWithCursorPagination(ctx, pagination)
   400  	assert.NoError(t, err)
   401  	assert.Equal(t, assets[4:7], got)
   402  	assert.Equal(t, entities.PageInfo{
   403  		HasNextPage:     true,
   404  		HasPreviousPage: true,
   405  		StartCursor:     assets[4].Cursor().Encode(),
   406  		EndCursor:       assets[6].Cursor().Encode(),
   407  	}, pageInfo)
   408  }
   409  
   410  func TestAssets_AssetStatusEnum(t *testing.T) {
   411  	ctx := tempTransaction(t)
   412  
   413  	bs := sqlstore.NewBlocks(connectionSource)
   414  	var assetStatus vegapb.Asset_Status
   415  
   416  	states := getEnums(t, assetStatus)
   417  	assert.Len(t, states, 5)
   418  
   419  	for e, state := range states {
   420  		t.Run(state, func(tt *testing.T) {
   421  			block := addTestBlock(t, ctx, bs)
   422  
   423  			as := sqlstore.NewAssets(connectionSource)
   424  
   425  			asset := getTestAsst(t, block)
   426  			asset.Status = entities.AssetStatus(e)
   427  			err := as.Add(ctx, asset)
   428  			require.NoError(tt, err, "failed to add asset with state %s", state)
   429  
   430  			fetchedAsset, err := as.GetByID(ctx, asset.ID.String())
   431  			assert.NoError(tt, err)
   432  			assert.Equal(tt, entities.AssetStatus(e), fetchedAsset.Status)
   433  		})
   434  	}
   435  }